diff --git a/docs/auth/authenticate.md b/docs/auth/authenticate.md index 740e5f3c1..d2fb11603 100644 --- a/docs/auth/authenticate.md +++ b/docs/auth/authenticate.md @@ -77,7 +77,7 @@ client := copilot.NewClient(nil) .NET ```csharp -using GitHub.Copilot.SDK; +using GitHub.Copilot; // Default: uses logged-in user credentials await using var client = new CopilotClient(); @@ -179,23 +179,23 @@ client := copilot.NewClient(&copilot.ClientOptions{ ```csharp -using GitHub.Copilot.SDK; +using GitHub.Copilot; var userAccessToken = "token"; await using var client = new CopilotClient(new CopilotClientOptions { - GithubToken = userAccessToken, + GitHubToken = userAccessToken, UseLoggedInUser = false, }); ``` ```csharp -using GitHub.Copilot.SDK; +using GitHub.Copilot; await using var client = new CopilotClient(new CopilotClientOptions { - GithubToken = userAccessToken, // Token from OAuth flow + GitHubToken = userAccessToken, // Token from OAuth flow UseLoggedInUser = false, // Don't use stored CLI credentials }); ``` diff --git a/docs/auth/byok.md b/docs/auth/byok.md index 95b4a1c74..cb2f8cb90 100644 --- a/docs/auth/byok.md +++ b/docs/auth/byok.md @@ -142,7 +142,7 @@ func main() { .NET ```csharp -using GitHub.Copilot.SDK; +using GitHub.Copilot; await using var client = new CopilotClient(); await using var session = await client.CreateSessionAsync(new SessionConfig @@ -424,7 +424,7 @@ func main() { .NET ```csharp -using GitHub.Copilot.SDK; +using GitHub.Copilot; var client = new CopilotClient(new CopilotClientOptions { diff --git a/docs/features/custom-agents.md b/docs/features/custom-agents.md index 71fd9b4b1..3d93f7589 100644 --- a/docs/features/custom-agents.md +++ b/docs/features/custom-agents.md @@ -173,7 +173,7 @@ session, _ := client.CreateSession(ctx, &copilot.SessionConfig{ .NET ```csharp -using GitHub.Copilot.SDK; +using GitHub.Copilot; await using var client = new CopilotClient(); await using var session = await client.CreateSessionAsync(new SessionConfig @@ -585,13 +585,13 @@ _, err := session.SendAndWait(ctx, copilot.MessageOptions{ ```csharp -using GitHub.Copilot.SDK; +using GitHub.Copilot; public static class SubAgentEventsExample { public static async Task Example(CopilotSession session) { - using var subscription = session.On(evt => + using var subscription = session.On(evt => { switch (evt) { @@ -622,7 +622,7 @@ public static class SubAgentEventsExample ```csharp -using var subscription = session.On(evt => +using var subscription = session.On(evt => { switch (evt) { diff --git a/docs/features/hooks.md b/docs/features/hooks.md index db9ad72ec..c88c6e605 100644 --- a/docs/features/hooks.md +++ b/docs/features/hooks.md @@ -146,7 +146,7 @@ session, err := client.CreateSession(ctx, &copilot.SessionConfig{ ```csharp -using GitHub.Copilot.SDK; +using GitHub.Copilot; public static class HooksExample { @@ -348,7 +348,7 @@ session, _ := client.CreateSession(ctx, &copilot.SessionConfig{ ```csharp -using GitHub.Copilot.SDK; +using GitHub.Copilot; public static class PermissionControlExample { diff --git a/docs/features/image-input.md b/docs/features/image-input.md index 286414c91..4aa564558 100644 --- a/docs/features/image-input.md +++ b/docs/features/image-input.md @@ -161,7 +161,7 @@ session.Send(ctx, copilot.MessageOptions{ ```csharp -using GitHub.Copilot.SDK; +using GitHub.Copilot; public static class ImageInputExample { @@ -193,7 +193,7 @@ public static class ImageInputExample ```csharp -using GitHub.Copilot.SDK; +using GitHub.Copilot; await using var client = new CopilotClient(); await using var session = await client.CreateSessionAsync(new SessionConfig @@ -376,7 +376,7 @@ session.Send(ctx, copilot.MessageOptions{ ```csharp -using GitHub.Copilot.SDK; +using GitHub.Copilot; public static class BlobAttachmentExample { diff --git a/docs/features/mcp.md b/docs/features/mcp.md index f6c5a7d7e..6f715bd2e 100644 --- a/docs/features/mcp.md +++ b/docs/features/mcp.md @@ -136,7 +136,7 @@ func main() { ### .NET ```csharp -using GitHub.Copilot.SDK; +using GitHub.Copilot; await using var client = new CopilotClient(); await using var session = await client.CreateSessionAsync(new SessionConfig diff --git a/docs/features/session-persistence.md b/docs/features/session-persistence.md index 374497711..5a1987227 100644 --- a/docs/features/session-persistence.md +++ b/docs/features/session-persistence.md @@ -109,7 +109,7 @@ session.SendAndWait(ctx, copilot.MessageOptions{Prompt: "Analyze my codebase"}) ### C# (.NET) ```csharp -using GitHub.Copilot.SDK; +using GitHub.Copilot; var client = new CopilotClient(); @@ -201,7 +201,7 @@ session.SendAndWait(ctx, copilot.MessageOptions{Prompt: "What did we discuss ear ```csharp -using GitHub.Copilot.SDK; +using GitHub.Copilot; public static class ResumeSessionExample { diff --git a/docs/features/skills.md b/docs/features/skills.md index 6db0d60f3..516c11762 100644 --- a/docs/features/skills.md +++ b/docs/features/skills.md @@ -116,7 +116,7 @@ func main() { .NET ```csharp -using GitHub.Copilot.SDK; +using GitHub.Copilot; await using var client = new CopilotClient(); await using var session = await client.CreateSessionAsync(new SessionConfig @@ -244,7 +244,7 @@ session, _ := client.CreateSession(context.Background(), &copilot.SessionConfig{ ```csharp -using GitHub.Copilot.SDK; +using GitHub.Copilot; public static class SkillsExample { diff --git a/docs/features/steering-and-queueing.md b/docs/features/steering-and-queueing.md index 7858a7d3f..ce4f4fba2 100644 --- a/docs/features/steering-and-queueing.md +++ b/docs/features/steering-and-queueing.md @@ -152,7 +152,7 @@ func main() { .NET ```csharp -using GitHub.Copilot.SDK; +using GitHub.Copilot; await using var client = new CopilotClient(); await using var session = await client.CreateSessionAsync(new SessionConfig @@ -361,7 +361,7 @@ session.Send(ctx, copilot.MessageOptions{ ```csharp -using GitHub.Copilot.SDK; +using GitHub.Copilot; public static class QueueingExample { diff --git a/docs/features/streaming-events.md b/docs/features/streaming-events.md index a12440ee5..6bc560a48 100644 --- a/docs/features/streaming-events.md +++ b/docs/features/streaming-events.md @@ -161,13 +161,13 @@ session.On(func(event copilot.SessionEvent) { ```csharp -using GitHub.Copilot.SDK; +using GitHub.Copilot; public static class StreamingEventsExample { public static async Task Example(CopilotSession session) { - session.On(evt => + session.On(evt => { if (evt is AssistantMessageDeltaEvent delta) { @@ -180,7 +180,7 @@ public static class StreamingEventsExample ```csharp -session.On(evt => +session.On(evt => { if (evt is AssistantMessageDeltaEvent delta) { diff --git a/docs/getting-started.md b/docs/getting-started.md index 0836f2567..3a43fad7c 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -299,7 +299,7 @@ cargo run Create a new console project and add this to `Program.cs`: ```csharp -using GitHub.Copilot.SDK; +using GitHub.Copilot; await using var client = new CopilotClient(); await using var session = await client.CreateSessionAsync(new SessionConfig @@ -557,7 +557,7 @@ async fn main() -> Result<(), Box> { Update `Program.cs`: ```csharp -using GitHub.Copilot.SDK; +using GitHub.Copilot; await using var client = new CopilotClient(); await using var session = await client.CreateSessionAsync(new SessionConfig @@ -568,7 +568,7 @@ await using var session = await client.CreateSessionAsync(new SessionConfig }); // Listen for response chunks -session.On(ev => +session.On(ev => { if (ev is AssistantMessageDeltaEvent deltaEvent) { @@ -800,17 +800,17 @@ tokio::spawn(async move { ```csharp -using GitHub.Copilot.SDK; +using GitHub.Copilot; public static class EventSubscriptionExample { public static void Example(CopilotSession session) { // Subscribe to all events - var unsubscribe = session.On(ev => Console.WriteLine($"Event: {ev.Type}")); + var unsubscribe = session.On(ev => Console.WriteLine($"Event: {ev.Type}")); // Filter by event type using pattern matching - session.On(ev => + session.On(ev => { switch (ev) { @@ -832,10 +832,10 @@ public static class EventSubscriptionExample ```csharp // Subscribe to all events -var unsubscribe = session.On(ev => Console.WriteLine($"Event: {ev.Type}")); +var unsubscribe = session.On(ev => Console.WriteLine($"Event: {ev.Type}")); // Filter by event type using pattern matching -session.On(ev => +session.On(ev => { switch (ev) { @@ -1159,7 +1159,7 @@ async fn main() -> Result<(), Box> { Update `Program.cs`: ```csharp -using GitHub.Copilot.SDK; +using GitHub.Copilot; using Microsoft.Extensions.AI; using System.ComponentModel; @@ -1190,7 +1190,7 @@ await using var session = await client.CreateSessionAsync(new SessionConfig Tools = [getWeather], }); -session.On(ev => +session.On(ev => { if (ev is AssistantMessageDeltaEvent deltaEvent) { @@ -1647,7 +1647,7 @@ cargo run Create a new console project and update `Program.cs`: ```csharp -using GitHub.Copilot.SDK; +using GitHub.Copilot; using Microsoft.Extensions.AI; using System.ComponentModel; @@ -1676,7 +1676,7 @@ await using var session = await client.CreateSessionAsync(new SessionConfig }); // Listen for response chunks -session.On(ev => +session.On(ev => { if (ev is AssistantMessageDeltaEvent deltaEvent) { @@ -2067,12 +2067,11 @@ let session = client .NET ```csharp -using GitHub.Copilot.SDK; +using GitHub.Copilot; using var client = new CopilotClient(new CopilotClientOptions { - CliUrl = "localhost:4321", - UseStdio = false + Connection = RuntimeConnection.ForUri("localhost:4321"), }); // Use the client normally diff --git a/docs/hooks/error-handling.md b/docs/hooks/error-handling.md index 803032432..f36573f31 100644 --- a/docs/hooks/error-handling.md +++ b/docs/hooks/error-handling.md @@ -84,7 +84,7 @@ type ErrorOccurredHandler func( ```csharp -using GitHub.Copilot.SDK; +using GitHub.Copilot; public delegate Task ErrorOccurredHandler( ErrorOccurredHookInput input, @@ -226,7 +226,7 @@ session, _ := client.CreateSession(context.Background(), &copilot.SessionConfig{ ```csharp -using GitHub.Copilot.SDK; +using GitHub.Copilot; public static class ErrorHandlingExample { diff --git a/docs/hooks/hooks-overview.md b/docs/hooks/hooks-overview.md index a5f5981f4..460813c26 100644 --- a/docs/hooks/hooks-overview.md +++ b/docs/hooks/hooks-overview.md @@ -124,7 +124,7 @@ func main() { .NET ```csharp -using GitHub.Copilot.SDK; +using GitHub.Copilot; var client = new CopilotClient(); diff --git a/docs/hooks/post-tool-use.md b/docs/hooks/post-tool-use.md index 47262415f..f3c6f6799 100644 --- a/docs/hooks/post-tool-use.md +++ b/docs/hooks/post-tool-use.md @@ -84,7 +84,7 @@ type PostToolUseHandler func( ```csharp -using GitHub.Copilot.SDK; +using GitHub.Copilot; public delegate Task PostToolUseHandler( PostToolUseHookInput input, @@ -219,7 +219,7 @@ session, _ := client.CreateSession(context.Background(), &copilot.SessionConfig{ ```csharp -using GitHub.Copilot.SDK; +using GitHub.Copilot; public static class PostToolUseExample { diff --git a/docs/hooks/pre-tool-use.md b/docs/hooks/pre-tool-use.md index e3509dd0a..6e568d8a2 100644 --- a/docs/hooks/pre-tool-use.md +++ b/docs/hooks/pre-tool-use.md @@ -84,7 +84,7 @@ type PreToolUseHandler func( ```csharp -using GitHub.Copilot.SDK; +using GitHub.Copilot; public delegate Task PreToolUseHandler( PreToolUseHookInput input, @@ -228,7 +228,7 @@ session, _ := client.CreateSession(context.Background(), &copilot.SessionConfig{ ```csharp -using GitHub.Copilot.SDK; +using GitHub.Copilot; public static class PreToolUseExample { diff --git a/docs/hooks/session-lifecycle.md b/docs/hooks/session-lifecycle.md index b4ff502d5..83b75a32f 100644 --- a/docs/hooks/session-lifecycle.md +++ b/docs/hooks/session-lifecycle.md @@ -88,7 +88,7 @@ type SessionStartHandler func( ```csharp -using GitHub.Copilot.SDK; +using GitHub.Copilot; public delegate Task SessionStartHandler( SessionStartHookInput input, diff --git a/docs/hooks/user-prompt-submitted.md b/docs/hooks/user-prompt-submitted.md index d5965f4a1..79e34249d 100644 --- a/docs/hooks/user-prompt-submitted.md +++ b/docs/hooks/user-prompt-submitted.md @@ -84,7 +84,7 @@ type UserPromptSubmittedHandler func( ```csharp -using GitHub.Copilot.SDK; +using GitHub.Copilot; public delegate Task UserPromptSubmittedHandler( UserPromptSubmittedHookInput input, @@ -209,7 +209,7 @@ session, _ := client.CreateSession(context.Background(), &copilot.SessionConfig{ ```csharp -using GitHub.Copilot.SDK; +using GitHub.Copilot; public static class UserPromptSubmittedExample { diff --git a/docs/integrations/microsoft-agent-framework.md b/docs/integrations/microsoft-agent-framework.md index 4da47104c..1802ddd4b 100644 --- a/docs/integrations/microsoft-agent-framework.md +++ b/docs/integrations/microsoft-agent-framework.md @@ -74,7 +74,7 @@ Wrap the Copilot SDK client as a MAF agent with a single method call. The result ```csharp -using GitHub.Copilot.SDK; +using GitHub.Copilot; using Microsoft.Agents.AI; await using var copilotClient = new CopilotClient(); @@ -146,7 +146,7 @@ Extend your Copilot agent with custom function tools. Tools defined through the ```csharp -using GitHub.Copilot.SDK; +using GitHub.Copilot; using Microsoft.Extensions.AI; using Microsoft.Agents.AI; @@ -282,7 +282,7 @@ Run agents one after another, passing output from one to the next: ```csharp -using GitHub.Copilot.SDK; +using GitHub.Copilot; using Microsoft.Agents.AI; using Microsoft.Agents.AI.Orchestration; @@ -395,7 +395,7 @@ Run multiple agents in parallel and aggregate their results: ```csharp -using GitHub.Copilot.SDK; +using GitHub.Copilot; using Microsoft.Agents.AI; using Microsoft.Agents.AI.Orchestration; @@ -472,7 +472,7 @@ When building interactive applications, stream agent responses to show real-time ```csharp -using GitHub.Copilot.SDK; +using GitHub.Copilot; using Microsoft.Agents.AI; await using var copilotClient = new CopilotClient(); diff --git a/docs/setup/backend-services.md b/docs/setup/backend-services.md index 655453667..d9dd508e5 100644 --- a/docs/setup/backend-services.md +++ b/docs/setup/backend-services.md @@ -215,15 +215,14 @@ response, _ := session.SendAndWait(ctx, copilot.MessageOptions{Prompt: message}) ```csharp -using GitHub.Copilot.SDK; +using GitHub.Copilot; var userId = "user1"; var message = "Hello"; var client = new CopilotClient(new CopilotClientOptions { - CliUrl = "localhost:4321", - UseStdio = false, + Connection = RuntimeConnection.ForUri("localhost:4321"), }); await using var session = await client.CreateSessionAsync(new SessionConfig @@ -240,8 +239,7 @@ var response = await session.SendAndWaitAsync( ```csharp var client = new CopilotClient(new CopilotClientOptions { - CliUrl = "localhost:4321", - UseStdio = false, + Connection = RuntimeConnection.ForUri("localhost:4321"), }); await using var session = await client.CreateSessionAsync(new SessionConfig diff --git a/docs/setup/github-oauth.md b/docs/setup/github-oauth.md index 31a3b9001..6cba4a5b7 100644 --- a/docs/setup/github-oauth.md +++ b/docs/setup/github-oauth.md @@ -230,12 +230,12 @@ response, _ := session.SendAndWait(ctx, copilot.MessageOptions{Prompt: "Hello!"} ```csharp -using GitHub.Copilot.SDK; +using GitHub.Copilot; CopilotClient CreateClientForUser(string userToken) => new CopilotClient(new CopilotClientOptions { - GithubToken = userToken, + GitHubToken = userToken, UseLoggedInUser = false, }); @@ -257,7 +257,7 @@ var response = await session.SendAndWaitAsync( CopilotClient CreateClientForUser(string userToken) => new CopilotClient(new CopilotClientOptions { - GithubToken = userToken, + GitHubToken = userToken, UseLoggedInUser = false, }); diff --git a/docs/setup/local-cli.md b/docs/setup/local-cli.md index 28bef7b20..e7da4d937 100644 --- a/docs/setup/local-cli.md +++ b/docs/setup/local-cli.md @@ -139,7 +139,7 @@ if response != nil { ```csharp var client = new CopilotClient(new CopilotClientOptions { - CliPath = "/usr/local/bin/copilot", + Connection = RuntimeConnection.ForStdio(path: "/usr/local/bin/copilot"), }); await using var session = await client.CreateSessionAsync( diff --git a/docs/troubleshooting/debugging.md b/docs/troubleshooting/debugging.md index 7092899fd..f01beafc7 100644 --- a/docs/troubleshooting/debugging.md +++ b/docs/troubleshooting/debugging.md @@ -73,7 +73,7 @@ client := copilot.NewClient(&copilot.ClientOptions{ ```csharp -using GitHub.Copilot.SDK; +using GitHub.Copilot; using Microsoft.Extensions.Logging; // Using ILogger @@ -164,7 +164,7 @@ func main() { ```csharp var client = new CopilotClient(new CopilotClientOptions { - CliArgs = new[] { "--log-dir", "/path/to/logs" } + Connection = RuntimeConnection.ForStdio(args: new[] { "--log-dir", "/path/to/logs" }) }); ``` diff --git a/docs/troubleshooting/mcp-debugging.md b/docs/troubleshooting/mcp-debugging.md index eb98eb1bd..664826c6e 100644 --- a/docs/troubleshooting/mcp-debugging.md +++ b/docs/troubleshooting/mcp-debugging.md @@ -236,7 +236,7 @@ cd /expected/working/dir ```csharp -using GitHub.Copilot.SDK; +using GitHub.Copilot; public static class McpDotnetConfigExample { @@ -248,14 +248,14 @@ public static class McpDotnetConfigExample { Command = @"C:\Tools\MyServer\MyServer.exe", Args = new List(), - Cwd = @"C:\Tools\MyServer", + WorkingDirectory = @"C:\Tools\MyServer", Tools = new List { "*" }, }, ["my-dotnet-tool"] = new McpStdioServerConfig { Command = "dotnet", Args = new List { @"C:\Tools\MyTool\MyTool.dll" }, - Cwd = @"C:\Tools\MyTool", + WorkingDirectory = @"C:\Tools\MyTool", Tools = new List { "*" }, } }; @@ -269,7 +269,7 @@ public static class McpDotnetConfigExample { Command = @"C:\Tools\MyServer\MyServer.exe", // Full path with .exe Args = new List(), - Cwd = @"C:\Tools\MyServer", // Set working directory + WorkingDirectory = @"C:\Tools\MyServer", // Set working directory Tools = new List { "*" }, } @@ -278,7 +278,7 @@ public static class McpDotnetConfigExample { Command = "dotnet", Args = new List { @"C:\Tools\MyTool\MyTool.dll" }, - Cwd = @"C:\Tools\MyTool", + WorkingDirectory = @"C:\Tools\MyTool", Tools = new List { "*" }, } ``` @@ -287,7 +287,7 @@ public static class McpDotnetConfigExample ```csharp -using GitHub.Copilot.SDK; +using GitHub.Copilot; public static class McpNpxConfigExample { diff --git a/dotnet/README.md b/dotnet/README.md index 012c51c17..f01d87474 100644 --- a/dotnet/README.md +++ b/dotnet/README.md @@ -1,4 +1,4 @@ -# Copilot SDK +# Copilot SDK SDK for programmatic control of GitHub Copilot CLI. @@ -7,7 +7,7 @@ SDK for programmatic control of GitHub Copilot CLI. ## Installation ```bash -dotnet add package GitHub.Copilot.SDK +dotnet add package GitHub.Copilot ``` ## Run the Samples @@ -27,7 +27,7 @@ dotnet run --file dotnet/samples/ManualToolResume.cs ## Quick Start ```csharp -using GitHub.Copilot.SDK; +using GitHub.Copilot; // Create and start client await using var client = new CopilotClient(); @@ -43,7 +43,7 @@ await using var session = await client.CreateSessionAsync(new SessionConfig // Wait for the response using the session.idle event var done = new TaskCompletionSource(); -session.On(evt => +session.On(evt => { if (evt is AssistantMessageEvent msg) { @@ -72,20 +72,24 @@ new CopilotClient(CopilotClientOptions? options = null) **Options:** -- `CliPath` - Path to CLI executable (default: `COPILOT_CLI_PATH` env var, or bundled CLI) -- `CliArgs` - Extra arguments prepended before SDK-managed flags -- `CliUrl` - URL of existing CLI server to connect to (e.g., `"localhost:8080"`). When provided, the client will not spawn a CLI process. -- `Port` - Server port (default: 0 for random) -- `UseStdio` - Use stdio transport instead of TCP (default: true) -- `LogLevel` - Log level (default: "info") -- `AutoStart` - Auto-start server (default: true) -- `Cwd` - Working directory for the CLI process -- `CopilotHome` - Base directory for Copilot data (session state, config, etc.). Sets `COPILOT_HOME` on the spawned CLI process. When not set, the CLI defaults to `~/.copilot`. Useful in restricted environments where only specific directories are writable. Ignored when using `CliUrl`. -- `Environment` - Environment variables to pass to the CLI process -- `Logger` - `ILogger` instance for SDK logging +- `Connection` - How to connect to the Copilot runtime. Defaults to `null` (equivalent to `RuntimeConnection.ForStdio()` with the bundled runtime). See "RuntimeConnection" below. +- `LogLevel` - Runtime log level. Accepts well-known values `CopilotLogLevel.None`, `Error`, `Warning`, `Info`, `Debug`, `All`. Defaults to null (the runtime's own default). +- `WorkingDirectory` - Working directory for the runtime process. +- `BaseDirectory` - Base directory for Copilot data (session state, config, etc.). Sets `COPILOT_HOME` on the spawned runtime process. When not set, the runtime defaults to `~/.copilot`. Useful in restricted environments where only specific directories are writable. Ignored when connecting via `RuntimeConnection.ForUri(...)`. +- `EnableRemoteSessions` - Enables remote-session features. +- `Environment` - Environment variables to pass to the runtime process. +- `Logger` - `ILogger` instance for SDK logging. - `GitHubToken` - GitHub token for authentication. When provided, takes priority over other auth methods. -- `UseLoggedInUser` - Whether to use logged-in user for authentication (default: true, but false when `GitHubToken` is provided). Cannot be used with `CliUrl`. -- `Telemetry` - OpenTelemetry configuration for the CLI process. Providing this enables telemetry — no separate flag needed. See [Telemetry](#telemetry) below. +- `UseLoggedInUser` - Whether to use logged-in user for authentication (default: true, but false when `GitHubToken` is provided). Cannot be used with `RuntimeConnection.ForUri(...)`. +- `Telemetry` - OpenTelemetry configuration for the runtime process. Providing this enables telemetry — no separate flag needed. See [Telemetry](#telemetry) below. + +#### RuntimeConnection + +`CopilotClientOptions.Connection` describes how the SDK reaches a Copilot runtime. There are three flavors, all constructed via static factories: + +- `RuntimeConnection.ForStdio(path?, args?)` — spawns the runtime as a child process and communicates over stdio. This is the default when `Connection` is null. +- `RuntimeConnection.ForTcp(port = 0, connectionToken?, path?, args?)` — spawns the runtime as a child process listening on a TCP port. `port = 0` auto-allocates; if a non-zero port is already in use, startup fails (no fallback). Use `CopilotClient.RuntimePort` after `StartAsync` to read the assigned port. `connectionToken` is required if other clients will connect via `RuntimeConnection.ForUri(...)`. +- `RuntimeConnection.ForUri(url, connectionToken?)` — connects to an already-running runtime at `url` (e.g., `"localhost:8080"`). Does not spawn a process. #### Methods @@ -153,23 +157,19 @@ Get the ID of the session currently displayed in the TUI. Only available when co Request the TUI to switch to displaying the specified session. Only available in TUI+server mode. -##### `On(Action handler): IDisposable` +##### `OnLifecycle(Action handler): IDisposable where T : SessionLifecycleEvent` -Subscribe to all session lifecycle events. Returns an `IDisposable` that unsubscribes when disposed. +Subscribe to session lifecycle events. Pass a derived type to filter by kind, or `SessionLifecycleEvent` to receive every lifecycle event. Returns an `IDisposable` that unsubscribes when disposed. ```csharp -using var subscription = client.On(evt => +// Receive every lifecycle event: +using var subscription = client.OnLifecycle(evt => { Console.WriteLine($"Session {evt.SessionId}: {evt.Type}"); }); -``` -##### `On(string eventType, Action handler): IDisposable` - -Subscribe to a specific lifecycle event type. Use `SessionLifecycleEventTypes` constants. - -```csharp -using var subscription = client.On(SessionLifecycleEventTypes.Foreground, evt => +// Only receive foreground events: +using var foreground = client.OnLifecycle(evt => { Console.WriteLine($"Session {evt.SessionId} is now in foreground"); }); @@ -177,11 +177,11 @@ using var subscription = client.On(SessionLifecycleEventTypes.Foreground, evt => **Lifecycle Event Types:** -- `SessionLifecycleEventTypes.Created` - A new session was created -- `SessionLifecycleEventTypes.Deleted` - A session was deleted -- `SessionLifecycleEventTypes.Updated` - A session was updated -- `SessionLifecycleEventTypes.Foreground` - A session became the foreground session in TUI -- `SessionLifecycleEventTypes.Background` - A session is no longer the foreground session +- `SessionCreatedEvent` — A new session was created +- `SessionDeletedEvent` — A session was deleted +- `SessionUpdatedEvent` — A session was updated +- `SessionForegroundEvent` — A session became the foreground session in TUI +- `SessionBackgroundEvent` — A session is no longer the foreground session --- @@ -208,12 +208,12 @@ Send a message to the session. Returns the message ID. -##### `On(SessionEventHandler handler): IDisposable` +##### `On(Action handler): IDisposable` Subscribe to session events. Returns a disposable to unsubscribe. ```csharp -var subscription = session.On(evt => +var subscription = session.On(evt => { Console.WriteLine($"Event: {evt.Type}"); }); @@ -226,7 +226,7 @@ subscription.Dispose(); Abort the currently processing message in this session. -##### `GetMessagesAsync(): Task>` +##### `GetEventsAsync(): Task>` Get all events/messages from this session. @@ -262,7 +262,7 @@ Sessions emit various events during processing. Each event type is a class that Use pattern matching to handle specific event types: ```csharp -session.On(evt => +session.On(evt => { switch (evt) { @@ -330,7 +330,7 @@ var session = await client.CreateSessionAsync(new SessionConfig // Use TaskCompletionSource to wait for completion var done = new TaskCompletionSource(); -session.On(evt => +session.On(evt => { switch (evt) { @@ -558,7 +558,7 @@ if (session.Capabilities.Ui?.Elicitation == true) ["production", "staging", "dev"]); // Text input — returns string or null - string? name = await session.Ui.InputAsync("Project name:", new InputOptions + string? name = await session.Ui.InputAsync("Project name:", new UiInputOptions { Title = "Name", MinLength = 1, @@ -566,7 +566,7 @@ if (session.Capabilities.Ui?.Elicitation == true) }); // Generic elicitation with full schema control - ElicitationResult result = await session.Ui.ElicitationAsync(new ElicitationParams + ElicitationResult result = await session.Ui.ElicitAsync(new ElicitationParams { Message = "Configure deployment", RequestedSchema = new ElicitationSchema @@ -738,7 +738,7 @@ An `OnPermissionRequest` handler is optional when you create or resume a session Use the built-in `PermissionHandler.ApproveAll` helper to allow every tool call without any checks: ```csharp -using GitHub.Copilot.SDK; +using GitHub.Copilot; var session = await client.CreateSessionAsync(new SessionConfig { @@ -749,7 +749,7 @@ var session = await client.CreateSessionAsync(new SessionConfig ### Custom Permission Handler -Provide your own `PermissionRequestHandler` delegate to inspect each request and apply custom logic: +Provide your own permission handler (`Func>`) to inspect each request and apply custom logic: ```csharp var session = await client.CreateSessionAsync(new SessionConfig @@ -987,7 +987,7 @@ catch (Exception ex) ## Requirements - .NET 8.0 or later -- GitHub Copilot CLI installed and in PATH (or provide custom `CliPath`) +- GitHub Copilot CLI installed and in PATH (or provide custom `Connection = RuntimeConnection.ForStdio(path: ...)`) ## License diff --git a/dotnet/samples/Chat.cs b/dotnet/samples/Chat.cs index 6345dd05c..f748a4005 100644 --- a/dotnet/samples/Chat.cs +++ b/dotnet/samples/Chat.cs @@ -1,6 +1,6 @@ #:project ../src/GitHub.Copilot.SDK.csproj -using GitHub.Copilot.SDK; +using GitHub.Copilot; await using var client = new CopilotClient(); await using var session = await client.CreateSessionAsync(new SessionConfig @@ -8,7 +8,7 @@ OnPermissionRequest = PermissionHandler.ApproveAll }); -using var _ = session.On(evt => +using var _ = session.On(evt => { Console.ForegroundColor = ConsoleColor.Blue; switch (evt) diff --git a/dotnet/samples/ManualToolResume.cs b/dotnet/samples/ManualToolResume.cs index 7658dde11..becda7444 100644 --- a/dotnet/samples/ManualToolResume.cs +++ b/dotnet/samples/ManualToolResume.cs @@ -1,8 +1,8 @@ #:project ../src/GitHub.Copilot.SDK.csproj using System.ComponentModel; -using GitHub.Copilot.SDK; -using GitHub.Copilot.SDK.Rpc; +using GitHub.Copilot; +using GitHub.Copilot.Rpc; using Microsoft.Extensions.AI; var tool = ManualToolDeclaration(); @@ -80,7 +80,7 @@ static async Task WaitForEventAsync(CopilotSession session, Func? { var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); IDisposable? subscription = null; - subscription = session.On(evt => + subscription = session.On(evt => { if (evt is T typed && (predicate?.Invoke(typed) ?? true)) { diff --git a/dotnet/src/ActionDisposable.cs b/dotnet/src/ActionDisposable.cs index 815904c12..86230651e 100644 --- a/dotnet/src/ActionDisposable.cs +++ b/dotnet/src/ActionDisposable.cs @@ -2,7 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ -namespace GitHub.Copilot.SDK; +namespace GitHub.Copilot; /// /// A disposable that invokes an action when disposed. diff --git a/dotnet/src/Client.cs b/dotnet/src/Client.cs index f314a519b..11f8c90c9 100644 --- a/dotnet/src/Client.cs +++ b/dotnet/src/Client.cs @@ -1,8 +1,8 @@ -/*--------------------------------------------------------------------------------------------- +/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ -using GitHub.Copilot.SDK.Rpc; +using GitHub.Copilot.Rpc; using Microsoft.Extensions.AI; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; @@ -17,7 +17,7 @@ using System.Text.Json.Serialization; using System.Text.RegularExpressions; -namespace GitHub.Copilot.SDK; +namespace GitHub.Copilot; /// /// Provides a client for interacting with the Copilot CLI server. @@ -41,7 +41,7 @@ namespace GitHub.Copilot.SDK; /// await using var session = await client.CreateSessionAsync(new() { OnPermissionRequest = PermissionHandler.ApproveAll, Model = "gpt-4" }); /// /// // Handle events -/// using var subscription = session.On(evt => +/// using var subscription = session.On<SessionEvent>(evt => /// { /// if (evt is AssistantMessageEvent assistantMessage) /// Console.WriteLine(assistantMessage.Data?.Content); @@ -71,28 +71,28 @@ public sealed partial class CopilotClient : IDisposable, IAsyncDisposable internal readonly ConcurrentDictionary _sessions = new(); private readonly CopilotClientOptions _options; + private readonly RuntimeConnection _connection; private readonly ILogger _logger; private Task? _connectionTask; - private volatile bool _disconnected; private bool _disposed; private readonly int? _optionsPort; private readonly string? _optionsHost; - private readonly string? _effectiveConnectionToken; private int? _actualPort; private int? _negotiatedProtocolVersion; private List? _modelsCache; private readonly SemaphoreSlim _modelsCacheLock = new(1, 1); private readonly Func>>? _onListModels; - private readonly List> _lifecycleHandlers = []; - private readonly Dictionary>> _typedLifecycleHandlers = []; + private readonly List _lifecycleHandlers = []; private readonly object _lifecycleHandlersLock = new(); private ServerRpc? _serverRpc; + private sealed record LifecycleSubscription(Type EventType, Action Handler); + /// /// Gets the typed RPC client for server-scoped methods (no session required). /// /// - /// The client must be started before accessing this property. Use or set to true. + /// The client must be started before accessing this property. Call before use. /// /// Thrown if the client has been disposed. /// Thrown if the client is not started. @@ -101,91 +101,77 @@ public sealed partial class CopilotClient : IDisposable, IAsyncDisposable : _serverRpc ?? throw new InvalidOperationException("Client is not started. Call StartAsync first."); /// - /// Gets the actual TCP port the CLI server is listening on, if using TCP transport. + /// Gets the actual TCP port the runtime is listening on, if using TCP transport. /// - public int? ActualPort => _actualPort; + public int? RuntimePort => _actualPort; /// /// Creates a new instance of . /// /// Options for creating the client. If null, default options are used. - /// Thrown when mutually exclusive options are provided (e.g., CliUrl with UseStdio or CliPath). /// /// - /// // Default options - spawns CLI server using stdio + /// // Default options - spawns the bundled runtime using stdio /// var client = new CopilotClient(); /// - /// // Connect to an existing server - /// var client = new CopilotClient(new CopilotClientOptions { CliUrl = "localhost:3000", UseStdio = false }); + /// // Connect to an existing runtime + /// var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri("localhost:3000") }); /// - /// // Custom CLI path with specific log level + /// // Custom runtime path with specific log level /// var client = new CopilotClient(new CopilotClientOptions /// { - /// CliPath = "/usr/local/bin/copilot", - /// LogLevel = "debug" + /// Connection = RuntimeConnection.ForStdio(path: "/usr/local/bin/copilot"), + /// LogLevel = CopilotLogLevel.Debug /// }); /// /// public CopilotClient(CopilotClientOptions? options = null) { _options = options ?? new(); + _connection = _options.Connection ?? RuntimeConnection.ForStdio(); - // Validate mutually exclusive options - if (!string.IsNullOrEmpty(_options.CliUrl) && (_options.UseStdio == true || _options.CliPath != null)) + switch (_connection) { - throw new ArgumentException("CliUrl is mutually exclusive with UseStdio and CliPath"); - } + case StdioRuntimeConnection: + break; - // When CliUrl is provided, force TCP mode (we connect to an external server, not spawn one) - if (!string.IsNullOrEmpty(_options.CliUrl)) - { - _options.UseStdio = false; - } - else - { - _options.UseStdio ??= true; - } + case TcpRuntimeConnection tcp: + if (tcp.ConnectionToken is { Length: 0 }) + { + throw new ArgumentException("ConnectionToken must be a non-empty string or null.", nameof(options)); + } + // Auto-generate a connection token when the SDK spawns the runtime over TCP + // so the loopback listener is safe by default. + tcp.ConnectionToken ??= Guid.NewGuid().ToString(); + break; - // Validate auth options with external server - if (!string.IsNullOrEmpty(_options.CliUrl) && (!string.IsNullOrEmpty(_options.GitHubToken) || _options.UseLoggedInUser != null)) - { - throw new ArgumentException("GitHubToken and UseLoggedInUser cannot be used with CliUrl (external server manages its own auth)"); - } + case UriRuntimeConnection uri: + if (string.IsNullOrEmpty(uri.Url)) + { + throw new ArgumentException("UriRuntimeConnection.Url must be a non-empty string.", nameof(options)); + } + if (!string.IsNullOrEmpty(_options.GitHubToken) || _options.UseLoggedInUser != null) + { + throw new ArgumentException("GitHubToken and UseLoggedInUser cannot be combined with RuntimeConnection.ForUri (the existing runtime manages its own auth).", nameof(options)); + } + var parsed = ParseRuntimeUrl(uri.Url); + _optionsHost = parsed.Host; + _optionsPort = parsed.Port; + break; - if (_options.TcpConnectionToken is not null) - { - if (_options.TcpConnectionToken.Length == 0) - { - throw new ArgumentException("TcpConnectionToken must be a non-empty string"); - } - if (_options.UseStdio == true) - { - throw new ArgumentException("TcpConnectionToken cannot be used with UseStdio = true"); - } + default: + throw new ArgumentException($"Unsupported RuntimeConnection type: {_connection.GetType().Name}", nameof(options)); } - var sdkSpawnsCli = _options.UseStdio == false && string.IsNullOrEmpty(_options.CliUrl); - _effectiveConnectionToken = _options.TcpConnectionToken - ?? (sdkSpawnsCli ? Guid.NewGuid().ToString() : null); - _logger = _options.Logger ?? NullLogger.Instance; _onListModels = _options.OnListModels; - - // Parse CliUrl if provided - if (!string.IsNullOrEmpty(_options.CliUrl)) - { - var uri = ParseCliUrl(_options.CliUrl!); - _optionsHost = uri.Host; - _optionsPort = uri.Port; - } } /// - /// Parses a CLI URL into a URI with host and port. + /// Parses a runtime URL into a URI with host and port. /// /// The URL to parse. Supports formats: "port", "host:port", "http://host:port". - /// A containing the parsed host and port. - private static Uri ParseCliUrl(string url) + private static Uri ParseRuntimeUrl(string url) { // If it's just a port number, treat as localhost if (int.TryParse(url, out var port)) @@ -209,17 +195,12 @@ private static Uri ParseCliUrl(string url) /// A that can be used to cancel the operation. /// A representing the asynchronous operation. /// - /// /// If the server is not already running and the client is configured to spawn one (default), it will be started. - /// If connecting to an external server (via CliUrl), only establishes the connection. - /// - /// - /// This method is called automatically when creating a session if is true (default). - /// + /// If connecting to an external runtime (via RuntimeConnection.ForUri), only establishes the connection. /// /// /// - /// var client = new CopilotClient(new CopilotClientOptions { AutoStart = false }); + /// var client = new CopilotClient(); /// await client.StartAsync(); /// // Now ready to create sessions /// @@ -231,7 +212,6 @@ public Task StartAsync(CancellationToken cancellationToken = default) async Task StartCoreAsync(CancellationToken ct) { _logger.LogDebug("Starting Copilot client"); - _disconnected = false; var startTimestamp = Stopwatch.GetTimestamp(); Connection? connection = null; @@ -239,16 +219,16 @@ async Task StartCoreAsync(CancellationToken ct) try { - if (_optionsHost is not null && _optionsPort is not null) + if (_connection is UriRuntimeConnection) { - // External server (TCP) + // External runtime _actualPort = _optionsPort; connection = await ConnectToServerAsync(null, _optionsHost, _optionsPort, null, ct); } else { // Child process (stdio or TCP) - var (startedProcess, portOrNull, stderrBuffer) = await StartCliServerAsync(_options, _effectiveConnectionToken, _logger, ct); + var (startedProcess, portOrNull, stderrBuffer) = await StartCliServerAsync(ct); cliProcess = startedProcess; _actualPort = portOrNull; connection = await ConnectToServerAsync(cliProcess, portOrNull is null ? null : "localhost", portOrNull, stderrBuffer, ct); @@ -522,7 +502,7 @@ private static (SystemMessageConfig? wireConfig, DictionaryA task that resolves to provide the . /// /// Sessions maintain conversation state, handle events, and manage tool execution. - /// If the client is not connected and is enabled (default), + /// If the client is not connected, /// this will automatically start the connection. /// /// @@ -570,8 +550,8 @@ public async Task CreateSessionAsync(SessionConfig config, Cance session.RegisterPermissionHandler(config.OnPermissionRequest); session.RegisterCommands(config.Commands); session.RegisterElicitationHandler(config.OnElicitationRequest); - session.RegisterExitPlanModeHandler(config.OnExitPlanMode); - session.RegisterAutoModeSwitchHandler(config.OnAutoModeSwitch); + session.RegisterExitPlanModeHandler(config.OnExitPlanModeRequest); + session.RegisterAutoModeSwitchHandler(config.OnAutoModeSwitchRequest); if (config.OnUserInputRequest != null) { session.RegisterUserInputHandler(config.OnUserInputRequest); @@ -586,9 +566,9 @@ public async Task CreateSessionAsync(SessionConfig config, Cance } if (config.OnEvent != null) { - session.On(config.OnEvent); + session.On(config.OnEvent); } - ConfigureSessionFsHandlers(session, config.CreateSessionFsHandler); + ConfigureSessionFsHandlers(session, config.CreateSessionFsProvider); RegisterSession(session); session.StartProcessingEvents(); LoggingHelpers.LogTiming(_logger, LogLevel.Debug, null, @@ -616,8 +596,8 @@ public async Task CreateSessionAsync(SessionConfig config, Cance config.EnableSessionTelemetry, (bool?)true, config.OnUserInputRequest != null ? true : null, - config.OnExitPlanMode != null ? true : null, - config.OnAutoModeSwitch != null ? true : null, + config.OnExitPlanModeRequest != null ? true : null, + config.OnAutoModeSwitchRequest != null ? true : null, hasHooks ? true : null, config.WorkingDirectory, config.Streaming is true ? true : null, @@ -728,8 +708,8 @@ public async Task ResumeSessionAsync(string sessionId, ResumeSes session.RegisterPermissionHandler(config.OnPermissionRequest); session.RegisterCommands(config.Commands); session.RegisterElicitationHandler(config.OnElicitationRequest); - session.RegisterExitPlanModeHandler(config.OnExitPlanMode); - session.RegisterAutoModeSwitchHandler(config.OnAutoModeSwitch); + session.RegisterExitPlanModeHandler(config.OnExitPlanModeRequest); + session.RegisterAutoModeSwitchHandler(config.OnAutoModeSwitchRequest); if (config.OnUserInputRequest != null) { session.RegisterUserInputHandler(config.OnUserInputRequest); @@ -744,9 +724,9 @@ public async Task ResumeSessionAsync(string sessionId, ResumeSes } if (config.OnEvent != null) { - session.On(config.OnEvent); + session.On(config.OnEvent); } - ConfigureSessionFsHandlers(session, config.CreateSessionFsHandler); + ConfigureSessionFsHandlers(session, config.CreateSessionFsProvider); RegisterSession(session); session.StartProcessingEvents(); LoggingHelpers.LogTiming(_logger, LogLevel.Debug, null, @@ -774,13 +754,13 @@ public async Task ResumeSessionAsync(string sessionId, ResumeSes config.EnableSessionTelemetry, (bool?)true, config.OnUserInputRequest != null ? true : null, - config.OnExitPlanMode != null ? true : null, - config.OnAutoModeSwitch != null ? true : null, + config.OnExitPlanModeRequest != null ? true : null, + config.OnAutoModeSwitchRequest != null ? true : null, hasHooks ? true : null, config.WorkingDirectory, config.ConfigDir, config.EnableConfigDiscovery, - config.DisableResume is true ? true : null, + config.SuppressResumeEvent is true ? true : null, config.Streaming is true ? true : null, config.IncludeSubAgentStreamingEvents, config.McpServers, @@ -832,32 +812,6 @@ public async Task ResumeSessionAsync(string sessionId, ResumeSes return session; } - /// - /// Gets the current connection state of the client. - /// - /// - /// The current : Disconnected, Connecting, Connected, or Error. - /// - /// - /// - /// if (client.State == ConnectionState.Connected) - /// { - /// var session = await client.CreateSessionAsync(new() { OnPermissionRequest = PermissionHandler.ApproveAll }); - /// } - /// - /// - public ConnectionState State - { - get - { - if (_connectionTask == null) return ConnectionState.Disconnected; - if (_connectionTask.IsFaulted) return ConnectionState.Error; - if (!_connectionTask.IsCompleted) return ConnectionState.Connecting; - if (_disconnected) return ConnectionState.Disconnected; - return ConnectionState.Connected; - } - } - /// /// Validates the health of the connection by sending a ping request. /// @@ -1130,103 +1084,59 @@ public async Task SetForegroundSessionIdAsync(string sessionId, CancellationToke } /// - /// Subscribes to all session lifecycle events. - /// - /// - /// Lifecycle events are emitted when sessions are created, deleted, updated, - /// or change foreground/background state (in TUI+server mode). - /// - /// A callback function that receives lifecycle events. - /// An IDisposable that, when disposed, unsubscribes the handler. - /// - /// - /// using var subscription = client.On(evt => - /// { - /// Console.WriteLine($"Session {evt.SessionId}: {evt.Type}"); - /// }); - /// - /// - public IDisposable On(Action handler) - { - ArgumentNullException.ThrowIfNull(handler); - - lock (_lifecycleHandlersLock) - { - _lifecycleHandlers.Add(handler); - } - - return new ActionDisposable(() => - { - lock (_lifecycleHandlersLock) - { - _lifecycleHandlers.Remove(handler); - } - }); - } - - /// - /// Subscribes to a specific session lifecycle event type. + /// Subscribes to session lifecycle events of a specific kind. /// - /// The event type to listen for (use SessionLifecycleEventTypes constants). - /// A callback function that receives events of the specified type. - /// An IDisposable that, when disposed, unsubscribes the handler. + /// + /// The lifecycle event type to listen for. Pass a derived type such as + /// to filter by kind, or + /// to receive every lifecycle event. + /// + /// A callback invoked when a matching lifecycle event arrives. + /// An that, when disposed, unsubscribes the handler. /// /// - /// using var subscription = client.On(SessionLifecycleEventTypes.Foreground, evt => + /// using var sub = client.OnLifecycle<SessionForegroundEvent>(evt => /// { /// Console.WriteLine($"Session {evt.SessionId} is now in foreground"); /// }); /// /// - public IDisposable On(string eventType, Action handler) + public IDisposable OnLifecycle(Action handler) where T : SessionLifecycleEvent { - ArgumentNullException.ThrowIfNull(eventType); ArgumentNullException.ThrowIfNull(handler); + var subscription = new LifecycleSubscription(typeof(T), evt => handler((T)evt)); + lock (_lifecycleHandlersLock) { - if (!_typedLifecycleHandlers.TryGetValue(eventType, out var handlers)) - { - handlers = []; - _typedLifecycleHandlers[eventType] = handlers; - } - - handlers.Add(handler); + _lifecycleHandlers.Add(subscription); } return new ActionDisposable(() => { lock (_lifecycleHandlersLock) { - if (_typedLifecycleHandlers.TryGetValue(eventType, out var handlers)) - { - handlers.Remove(handler); - } + _lifecycleHandlers.Remove(subscription); } }); } private void DispatchLifecycleEvent(SessionLifecycleEvent evt) { - List> typedHandlers; - List> wildcardHandlers; - + List snapshot; lock (_lifecycleHandlersLock) { - typedHandlers = _typedLifecycleHandlers.TryGetValue(evt.Type, out var handlers) - ? [.. handlers] - : []; - wildcardHandlers = [.. _lifecycleHandlers]; - } - - foreach (var handler in typedHandlers) - { - try { handler(evt); } catch { /* Ignore handler errors */ } + snapshot = [.. _lifecycleHandlers]; } - foreach (var handler in wildcardHandlers) + var eventType = evt.GetType(); + foreach (var subscription in snapshot) { - try { handler(evt); } catch { /* Ignore handler errors */ } + if (!subscription.EventType.IsAssignableFrom(eventType)) + { + continue; + } + try { subscription.Handler(evt); } catch { /* Ignore handler errors */ } } } @@ -1309,11 +1219,6 @@ private static IOException CreateCliExitedException(string message, StringBuilde private Task EnsureConnectedAsync(CancellationToken cancellationToken) { - if (_connectionTask is null && !_options.AutoStart) - { - throw new InvalidOperationException($"Client not connected. Call {nameof(StartAsync)}() first."); - } - // If already started or starting, this will return the existing task return (Task)StartAsync(cancellationToken); } @@ -1343,11 +1248,11 @@ private void ConfigureSessionFsHandlers(CopilotSession session, Func tcp.ConnectionToken, + UriRuntimeConnection uri => uri.ConnectionToken, + _ => null, + }; var connectResponse = await InvokeRpcAsync( - connection.Rpc, "connect", [new ConnectRequest { Token = _effectiveConnectionToken }], connection.StderrBuffer, cancellationToken); + connection.Rpc, "connect", [new ConnectRequest { Token = token }], connection.StderrBuffer, cancellationToken); serverVersion = (int)connectResponse.ProtocolVersion; } catch (IOException ex) when (ex.InnerException is RemoteRpcException remoteEx && IsUnsupportedConnectMethod(remoteEx)) @@ -1410,32 +1321,42 @@ private static bool IsUnsupportedConnectMethod(RemoteRpcException ex) || string.Equals(ex.Message, "Unhandled method connect", StringComparison.Ordinal); } - private static async Task<(Process Process, int? DetectedLocalhostTcpPort, StringBuilder StderrBuffer)> StartCliServerAsync(CopilotClientOptions options, string? connectionToken, ILogger logger, CancellationToken cancellationToken) + private async Task<(Process Process, int? DetectedLocalhostTcpPort, StringBuilder StderrBuffer)> StartCliServerAsync(CancellationToken cancellationToken) { - // Use explicit path, COPILOT_CLI_PATH env var (from options.Environment or process env), or bundled CLI - no PATH fallback + var options = _options; + var logger = _logger; + var childProcessConnection = (ChildProcessRuntimeConnection)_connection; + var tcpConnection = _connection as TcpRuntimeConnection; + var useStdio = _connection is StdioRuntimeConnection; + + // Use explicit path, COPILOT_CLI_PATH env var (from options.Environment or process env), or bundled runtime - no PATH fallback var envCliPath = options.Environment is not null && options.Environment.TryGetValue("COPILOT_CLI_PATH", out var envValue) ? envValue : System.Environment.GetEnvironmentVariable("COPILOT_CLI_PATH"); - var cliPath = options.CliPath + var cliPath = childProcessConnection.Path ?? envCliPath ?? GetBundledCliPath(out var searchedPath) - ?? throw new InvalidOperationException($"Copilot CLI not found at '{searchedPath}'. Ensure the SDK NuGet package was restored correctly or provide an explicit CliPath."); - var cliPathSource = options.CliPath is not null ? "Options" : envCliPath is not null ? "Environment" : "Bundled"; + ?? throw new InvalidOperationException($"Copilot runtime not found at '{searchedPath}'. Ensure the SDK NuGet package was restored correctly or provide an explicit RuntimeConnection.ForStdio(path: ...) / RuntimeConnection.ForTcp(path: ...)."); + var cliPathSource = childProcessConnection.Path is not null ? "Options" : envCliPath is not null ? "Environment" : "Bundled"; var args = new List(); - if (options.CliArgs != null) + if (childProcessConnection.Args != null) { - args.AddRange(options.CliArgs); + args.AddRange(childProcessConnection.Args); } - args.AddRange(["--headless", "--no-auto-update", "--log-level", options.LogLevel]); + args.AddRange(["--headless", "--no-auto-update"]); + if (options.LogLevel is { } logLevel && !string.IsNullOrEmpty(logLevel.Value)) + { + args.AddRange(["--log-level", logLevel.Value]); + } - if (options.UseStdio == true) + if (useStdio) { args.Add("--stdio"); } - else if (options.Port > 0) + else if (tcpConnection is { Port: > 0 } tcp) { - args.AddRange(["--port", options.Port.ToString(CultureInfo.InvariantCulture)]); + args.AddRange(["--port", tcp.Port.ToString(CultureInfo.InvariantCulture)]); } // Add auth-related flags @@ -1456,24 +1377,24 @@ private static bool IsUnsupportedConnectMethod(RemoteRpcException ex) args.AddRange(["--session-idle-timeout", options.SessionIdleTimeoutSeconds.Value.ToString(CultureInfo.InvariantCulture)]); } - if (options.Remote) + if (options.EnableRemoteSessions) { args.Add("--remote"); } var (fileName, processArgs) = ResolveCliCommand(cliPath, args); - var configuredPort = options.UseStdio == true ? (int?)null : options.Port; - LogStartingCopilotCli(logger, cliPath, fileName, cliPathSource, options.UseStdio == true, configuredPort); + var configuredPort = useStdio ? (int?)null : tcpConnection?.Port; + LogStartingCopilotCli(logger, cliPath, fileName, cliPathSource, useStdio, configuredPort); var startInfo = new ProcessStartInfo { FileName = fileName, Arguments = string.Join(" ", processArgs.Select(ProcessArgumentEscaper.Escape)), UseShellExecute = false, - RedirectStandardInput = options.UseStdio == true, + RedirectStandardInput = useStdio, RedirectStandardOutput = true, RedirectStandardError = true, - WorkingDirectory = options.Cwd, + WorkingDirectory = options.WorkingDirectory, CreateNoWindow = true }; @@ -1494,14 +1415,14 @@ private static bool IsUnsupportedConnectMethod(RemoteRpcException ex) startInfo.Environment["COPILOT_SDK_AUTH_TOKEN"] = options.GitHubToken; } - if (!string.IsNullOrEmpty(connectionToken)) + if (tcpConnection?.ConnectionToken is { Length: > 0 } token) { - startInfo.Environment["COPILOT_CONNECTION_TOKEN"] = connectionToken; + startInfo.Environment["COPILOT_CONNECTION_TOKEN"] = token; } - if (!string.IsNullOrEmpty(options.CopilotHome)) + if (!string.IsNullOrEmpty(options.BaseDirectory)) { - startInfo.Environment["COPILOT_HOME"] = options.CopilotHome; + startInfo.Environment["COPILOT_HOME"] = options.BaseDirectory; } // Set telemetry environment variables if configured @@ -1547,7 +1468,7 @@ private static bool IsUnsupportedConnectMethod(RemoteRpcException ex) }, cancellationToken); var detectedLocalhostTcpPort = (int?)null; - if (options.UseStdio != true) + if (!useStdio) { // Wait for port announcement var portWaitTimestamp = Stopwatch.GetTimestamp(); @@ -1560,7 +1481,7 @@ private static bool IsUnsupportedConnectMethod(RemoteRpcException ex) if (line is null) { await stderrReader; - throw CreateCliExitedException("CLI process exited unexpectedly", stderrBuffer); + throw CreateCliExitedException("Runtime process exited unexpectedly", stderrBuffer); } if (logger.IsEnabled(LogLevel.Debug)) @@ -1640,11 +1561,11 @@ private async Task ConnectToServerAsync(Process? cliProcess, string? Stream inputStream, outputStream; NetworkStream? networkStream = null; - if (_options.UseStdio == true) + if (_connection is StdioRuntimeConnection) { if (cliProcess == null) { - throw new InvalidOperationException("CLI process not started"); + throw new InvalidOperationException("Runtime process not started"); } inputStream = cliProcess.StandardOutput.BaseStream; @@ -1708,9 +1629,6 @@ private async Task ConnectToServerAsync(Process? cliProcess, string? "CopilotClient.ConnectToServerAsync transport setup complete. Elapsed={Elapsed}", setupTimestamp); - // Transition state to Disconnected if the JSON-RPC connection drops - _ = rpc.Completion.ContinueWith(_ => _disconnected = true, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); - _serverRpc = new ServerRpc(rpc); return new Connection(rpc, cliProcess, networkStream, stderrBuffer); @@ -1730,7 +1648,7 @@ private static JsonSerializerOptions CreateSerializerOptions() options.TypeInfoResolverChain.Add(TypesJsonContext.Default); options.TypeInfoResolverChain.Add(CopilotSession.SessionJsonContext.Default); options.TypeInfoResolverChain.Add(SessionEventsJsonContext.Default); - options.TypeInfoResolverChain.Add(SDK.Rpc.RpcJsonContext.Default); + options.TypeInfoResolverChain.Add(GitHub.Copilot.Rpc.RpcJsonContext.Default); options.MakeReadOnly(); @@ -1798,13 +1716,19 @@ public void OnSessionEvent(string sessionId, JsonElement? @event) public void OnSessionLifecycle(string type, string sessionId, JsonElement? metadata) { - var evt = new SessionLifecycleEvent + SessionLifecycleEvent evt = type switch { - Type = type, - SessionId = sessionId + "session.created" => new SessionCreatedEvent(), + "session.deleted" => new SessionDeletedEvent(), + "session.updated" => new SessionUpdatedEvent(), + "session.foreground" => new SessionForegroundEvent(), + "session.background" => new SessionBackgroundEvent(), + _ => new SessionLifecycleEvent() }; - if (metadata != null) + evt.Type = type; + evt.SessionId = sessionId; + if (metadata is not null) { evt.Metadata = JsonSerializer.Deserialize( metadata.Value.GetRawText(), @@ -2083,7 +2007,7 @@ internal record ResumeSessionRequest( string? WorkingDirectory, string? ConfigDir, bool? EnableConfigDiscovery, - bool? DisableResume, + bool? SuppressResumeEvent, bool? Streaming, bool? IncludeSubAgentStreamingEvents, IDictionary? McpServers, @@ -2214,7 +2138,7 @@ internal partial class ClientJsonContext : JsonSerializerContext; /// back through Microsoft.Extensions.AI without JSON serialization. /// /// The tool result to wrap. -public class ToolResultAIContent(ToolResultObject toolResult) : AIContent +public sealed class ToolResultAIContent(ToolResultObject toolResult) : AIContent { /// /// Gets the underlying . diff --git a/dotnet/src/CopilotTool.cs b/dotnet/src/CopilotTool.cs index 6adcee093..feed5be76 100644 --- a/dotnet/src/CopilotTool.cs +++ b/dotnet/src/CopilotTool.cs @@ -4,7 +4,7 @@ using Microsoft.Extensions.AI; -namespace GitHub.Copilot.SDK; +namespace GitHub.Copilot; /// /// Provides helpers for defining Copilot tools. @@ -23,7 +23,7 @@ public static class CopilotTool /// The delegate to invoke when the tool is called. /// The Microsoft.Extensions.AI options used to create the function. /// Copilot-specific tool options. - /// An that can be added to or . + /// An that can be added to . /// /// This is a helper on top of that applies additional configuration to support /// Copilot tools, such as binding a parameter and adding Copilot-specific metadata properties based on the provided diff --git a/dotnet/src/Generated/Rpc.cs b/dotnet/src/Generated/Rpc.cs index 2bd4910a2..db7ce9d4c 100644 --- a/dotnet/src/Generated/Rpc.cs +++ b/dotnet/src/Generated/Rpc.cs @@ -16,7 +16,7 @@ using System.Text.Json.Serialization; using System.Threading; -namespace GitHub.Copilot.SDK.Rpc; +namespace GitHub.Copilot.Rpc; /// Server liveness response, including the echoed message, current server timestamp, and protocol version. public sealed class PingResult @@ -7204,13 +7204,13 @@ public sealed class Converter : JsonConverter /// public override ModelPickerCategory Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, ModelPickerCategory value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ModelPickerCategory)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ModelPickerCategory)); } } } @@ -7272,13 +7272,13 @@ public sealed class Converter : JsonConverter /// public override ModelPickerPriceCategory Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, ModelPickerPriceCategory value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ModelPickerPriceCategory)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ModelPickerPriceCategory)); } } } @@ -7337,13 +7337,13 @@ public sealed class Converter : JsonConverter /// public override ModelPolicyState Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, ModelPolicyState value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ModelPolicyState)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ModelPolicyState)); } } } @@ -7405,13 +7405,13 @@ public sealed class Converter : JsonConverter /// public override DiscoveredMcpServerType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, DiscoveredMcpServerType value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(DiscoveredMcpServerType)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(DiscoveredMcpServerType)); } } } @@ -7467,13 +7467,13 @@ public sealed class Converter : JsonConverter /// public override SessionFsSetProviderConventions Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, SessionFsSetProviderConventions value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionFsSetProviderConventions)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionFsSetProviderConventions)); } } } @@ -7530,13 +7530,13 @@ public sealed class Converter : JsonConverter public override ConnectedRemoteSessionMetadataKind Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, ConnectedRemoteSessionMetadataKind value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ConnectedRemoteSessionMetadataKind)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ConnectedRemoteSessionMetadataKind)); } } } @@ -7593,13 +7593,13 @@ public sealed class Converter : JsonConverter /// public override SessionContextHostType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, SessionContextHostType value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionContextHostType)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionContextHostType)); } } } @@ -7662,13 +7662,13 @@ public sealed class Converter : JsonConverter /// public override SendAgentMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, SendAgentMode value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SendAgentMode)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SendAgentMode)); } } } @@ -7728,13 +7728,13 @@ public sealed class Converter : JsonConverter /// public override SendAttachmentGithubReferenceType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, SendAttachmentGithubReferenceType value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SendAttachmentGithubReferenceType)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SendAttachmentGithubReferenceType)); } } } @@ -7791,13 +7791,13 @@ public sealed class Converter : JsonConverter /// public override SendMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, SendMode value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SendMode)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SendMode)); } } } @@ -7857,13 +7857,13 @@ public sealed class Converter : JsonConverter /// public override SessionLogLevel Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, SessionLogLevel value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionLogLevel)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionLogLevel)); } } } @@ -7935,13 +7935,13 @@ public sealed class Converter : JsonConverter /// public override AuthInfoType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, AuthInfoType value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AuthInfoType)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AuthInfoType)); } } } @@ -7998,13 +7998,13 @@ public sealed class Converter : JsonConverter public override WorkspacesWorkspaceDetailsHostType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, WorkspacesWorkspaceDetailsHostType value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(WorkspacesWorkspaceDetailsHostType)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(WorkspacesWorkspaceDetailsHostType)); } } } @@ -8067,13 +8067,13 @@ public sealed class Converter : JsonConverter /// public override InstructionsSourcesLocation Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, InstructionsSourcesLocation value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(InstructionsSourcesLocation)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(InstructionsSourcesLocation)); } } } @@ -8145,13 +8145,13 @@ public sealed class Converter : JsonConverter /// public override InstructionsSourcesType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, InstructionsSourcesType value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(InstructionsSourcesType)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(InstructionsSourcesType)); } } } @@ -8220,13 +8220,13 @@ public sealed class Converter : JsonConverter /// public override AgentInfoSource Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, AgentInfoSource value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AgentInfoSource)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AgentInfoSource)); } } } @@ -8283,13 +8283,13 @@ public sealed class Converter : JsonConverter /// public override TaskExecutionMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, TaskExecutionMode value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(TaskExecutionMode)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(TaskExecutionMode)); } } } @@ -8355,13 +8355,13 @@ public sealed class Converter : JsonConverter /// public override TaskStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, TaskStatus value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(TaskStatus)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(TaskStatus)); } } } @@ -8418,13 +8418,13 @@ public sealed class Converter : JsonConverter /// public override TaskShellInfoAttachmentMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, TaskShellInfoAttachmentMode value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(TaskShellInfoAttachmentMode)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(TaskShellInfoAttachmentMode)); } } } @@ -8484,13 +8484,13 @@ public sealed class Converter : JsonConverter /// public override McpSamplingExecutionAction Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, McpSamplingExecutionAction value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpSamplingExecutionAction)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpSamplingExecutionAction)); } } } @@ -8547,13 +8547,13 @@ public sealed class Converter : JsonConverter /// public override McpSetEnvValueModeDetails Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, McpSetEnvValueModeDetails value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpSetEnvValueModeDetails)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpSetEnvValueModeDetails)); } } } @@ -8610,13 +8610,13 @@ public sealed class Converter : JsonConverter /// public override OptionsUpdateEnvValueMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, OptionsUpdateEnvValueMode value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(OptionsUpdateEnvValueMode)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(OptionsUpdateEnvValueMode)); } } } @@ -8673,13 +8673,13 @@ public sealed class Converter : JsonConverter /// public override ExtensionSource Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, ExtensionSource value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ExtensionSource)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ExtensionSource)); } } } @@ -8742,13 +8742,13 @@ public sealed class Converter : JsonConverter /// public override ExtensionStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, ExtensionStatus value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ExtensionStatus)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ExtensionStatus)); } } } @@ -8802,13 +8802,13 @@ public sealed class Converter : JsonConverter /// public override SlashCommandInputCompletion Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, SlashCommandInputCompletion value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SlashCommandInputCompletion)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SlashCommandInputCompletion)); } } } @@ -8868,13 +8868,13 @@ public sealed class Converter : JsonConverter /// public override SlashCommandKind Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, SlashCommandKind value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SlashCommandKind)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SlashCommandKind)); } } } @@ -8934,13 +8934,13 @@ public sealed class Converter : JsonConverter /// public override UIElicitationResponseAction Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, UIElicitationResponseAction value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(UIElicitationResponseAction)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(UIElicitationResponseAction)); } } } @@ -9000,13 +9000,13 @@ public sealed class Converter : JsonConverter /// public override UIAutoModeSwitchResponse Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, UIAutoModeSwitchResponse value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(UIAutoModeSwitchResponse)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(UIAutoModeSwitchResponse)); } } } @@ -9069,13 +9069,13 @@ public sealed class Converter : JsonConverter /// public override UIExitPlanModeAction Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, UIExitPlanModeAction value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(UIExitPlanModeAction)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(UIExitPlanModeAction)); } } } @@ -9132,13 +9132,13 @@ public sealed class Converter : JsonConverter public override PermissionsConfigureAdditionalContentExclusionPolicyScope Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, PermissionsConfigureAdditionalContentExclusionPolicyScope value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PermissionsConfigureAdditionalContentExclusionPolicyScope)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PermissionsConfigureAdditionalContentExclusionPolicyScope)); } } } @@ -9201,13 +9201,13 @@ public sealed class Converter : JsonConverter /// public override PermissionsSetApproveAllSource Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, PermissionsSetApproveAllSource value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PermissionsSetApproveAllSource)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PermissionsSetApproveAllSource)); } } } @@ -9264,13 +9264,13 @@ public sealed class Converter : JsonConverter /// public override PermissionsModifyRulesScope Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, PermissionsModifyRulesScope value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PermissionsModifyRulesScope)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PermissionsModifyRulesScope)); } } } @@ -9327,13 +9327,13 @@ public sealed class Converter : JsonConverter /// public override PermissionLocationType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, PermissionLocationType value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PermissionLocationType)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PermissionLocationType)); } } } @@ -9393,13 +9393,13 @@ public sealed class Converter : JsonConverter /// public override MetadataSnapshotCurrentMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, MetadataSnapshotCurrentMode value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(MetadataSnapshotCurrentMode)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(MetadataSnapshotCurrentMode)); } } } @@ -9456,13 +9456,13 @@ public sealed class Converter : JsonConverter public override MetadataSnapshotRemoteMetadataTaskType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, MetadataSnapshotRemoteMetadataTaskType value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(MetadataSnapshotRemoteMetadataTaskType)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(MetadataSnapshotRemoteMetadataTaskType)); } } } @@ -9519,13 +9519,13 @@ public sealed class Converter : JsonConverter /// public override WorkspaceSummaryHostType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, WorkspaceSummaryHostType value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(WorkspaceSummaryHostType)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(WorkspaceSummaryHostType)); } } } @@ -9582,13 +9582,13 @@ public sealed class Converter : JsonConverter public override SessionWorkingDirectoryContextHostType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, SessionWorkingDirectoryContextHostType value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionWorkingDirectoryContextHostType)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionWorkingDirectoryContextHostType)); } } } @@ -9648,13 +9648,13 @@ public sealed class Converter : JsonConverter /// public override ShellKillSignal Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, ShellKillSignal value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ShellKillSignal)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ShellKillSignal)); } } } @@ -9711,13 +9711,13 @@ public sealed class Converter : JsonConverter /// public override QueuePendingItemsKind Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, QueuePendingItemsKind value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(QueuePendingItemsKind)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(QueuePendingItemsKind)); } } } @@ -9774,13 +9774,13 @@ public sealed class Converter : JsonConverter /// public override EventsCursorStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, EventsCursorStatus value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(EventsCursorStatus)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(EventsCursorStatus)); } } } @@ -9837,13 +9837,13 @@ public sealed class Converter : JsonConverter /// public override EventsAgentScope Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, EventsAgentScope value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(EventsAgentScope)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(EventsAgentScope)); } } } @@ -9903,13 +9903,13 @@ public sealed class Converter : JsonConverter /// public override RemoteSessionMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, RemoteSessionMode value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(RemoteSessionMode)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(RemoteSessionMode)); } } } @@ -9965,13 +9965,13 @@ public sealed class Converter : JsonConverter /// public override SessionFsErrorCode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, SessionFsErrorCode value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionFsErrorCode)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionFsErrorCode)); } } } @@ -10027,13 +10027,13 @@ public sealed class Converter : JsonConverter public override SessionFsReaddirWithTypesEntryType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, SessionFsReaddirWithTypesEntryType value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionFsReaddirWithTypesEntryType)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionFsReaddirWithTypesEntryType)); } } } @@ -10092,13 +10092,13 @@ public sealed class Converter : JsonConverter /// public override SessionFsSqliteQueryType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, SessionFsSqliteQueryType value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionFsSqliteQueryType)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionFsSqliteQueryType)); } } } @@ -13087,225 +13087,225 @@ public static void RegisterClientSessionApiHandlers(JsonRpc rpc, Func /// Provides the base class from which all session events derive. @@ -5345,13 +5345,13 @@ public sealed class Converter : JsonConverter /// public override WorkingDirectoryContextHostType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, WorkingDirectoryContextHostType value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(WorkingDirectoryContextHostType)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(WorkingDirectoryContextHostType)); } } } @@ -5409,13 +5409,13 @@ public sealed class Converter : JsonConverter /// public override ReasoningSummary Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, ReasoningSummary value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ReasoningSummary)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ReasoningSummary)); } } } @@ -5473,13 +5473,13 @@ public sealed class Converter : JsonConverter /// public override SessionMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, SessionMode value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionMode)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionMode)); } } } @@ -5537,13 +5537,13 @@ public sealed class Converter : JsonConverter /// public override PlanChangedOperation Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, PlanChangedOperation value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PlanChangedOperation)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PlanChangedOperation)); } } } @@ -5598,13 +5598,13 @@ public sealed class Converter : JsonConverter /// public override WorkspaceFileChangedOperation Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, WorkspaceFileChangedOperation value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(WorkspaceFileChangedOperation)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(WorkspaceFileChangedOperation)); } } } @@ -5659,13 +5659,13 @@ public sealed class Converter : JsonConverter /// public override HandoffSourceType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, HandoffSourceType value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(HandoffSourceType)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(HandoffSourceType)); } } } @@ -5720,13 +5720,13 @@ public sealed class Converter : JsonConverter /// public override ShutdownType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, ShutdownType value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ShutdownType)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ShutdownType)); } } } @@ -5787,13 +5787,13 @@ public sealed class Converter : JsonConverter /// public override UserMessageAgentMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, UserMessageAgentMode value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(UserMessageAgentMode)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(UserMessageAgentMode)); } } } @@ -5851,13 +5851,13 @@ public sealed class Converter : JsonConverter public override UserMessageAttachmentGithubReferenceType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, UserMessageAttachmentGithubReferenceType value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(UserMessageAttachmentGithubReferenceType)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(UserMessageAttachmentGithubReferenceType)); } } } @@ -5912,13 +5912,13 @@ public sealed class Converter : JsonConverter /// public override AssistantMessageToolRequestType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, AssistantMessageToolRequestType value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AssistantMessageToolRequestType)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AssistantMessageToolRequestType)); } } } @@ -5979,13 +5979,13 @@ public sealed class Converter : JsonConverter /// public override AssistantUsageApiEndpoint Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, AssistantUsageApiEndpoint value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AssistantUsageApiEndpoint)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AssistantUsageApiEndpoint)); } } } @@ -6043,13 +6043,13 @@ public sealed class Converter : JsonConverter /// public override ModelCallFailureSource Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, ModelCallFailureSource value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ModelCallFailureSource)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ModelCallFailureSource)); } } } @@ -6107,13 +6107,13 @@ public sealed class Converter : JsonConverter /// public override AbortReason Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, AbortReason value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AbortReason)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AbortReason)); } } } @@ -6168,13 +6168,13 @@ public sealed class Converter : JsonConverter public override ToolExecutionCompleteContentResourceLinkIconTheme Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, ToolExecutionCompleteContentResourceLinkIconTheme value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ToolExecutionCompleteContentResourceLinkIconTheme)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ToolExecutionCompleteContentResourceLinkIconTheme)); } } } @@ -6229,13 +6229,13 @@ public sealed class Converter : JsonConverter /// public override SystemMessageRole Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, SystemMessageRole value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SystemMessageRole)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SystemMessageRole)); } } } @@ -6290,13 +6290,13 @@ public sealed class Converter : JsonConverter public override SystemNotificationAgentCompletedStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, SystemNotificationAgentCompletedStatus value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SystemNotificationAgentCompletedStatus)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SystemNotificationAgentCompletedStatus)); } } } @@ -6351,13 +6351,13 @@ public sealed class Converter : JsonConverter /// public override PermissionRequestMemoryAction Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, PermissionRequestMemoryAction value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PermissionRequestMemoryAction)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PermissionRequestMemoryAction)); } } } @@ -6412,13 +6412,13 @@ public sealed class Converter : JsonConverter /// public override PermissionRequestMemoryDirection Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, PermissionRequestMemoryDirection value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PermissionRequestMemoryDirection)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PermissionRequestMemoryDirection)); } } } @@ -6476,13 +6476,13 @@ public sealed class Converter : JsonConverter public override PermissionPromptRequestPathAccessKind Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, PermissionPromptRequestPathAccessKind value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PermissionPromptRequestPathAccessKind)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PermissionPromptRequestPathAccessKind)); } } } @@ -6537,13 +6537,13 @@ public sealed class Converter : JsonConverter /// public override ElicitationRequestedMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, ElicitationRequestedMode value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ElicitationRequestedMode)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ElicitationRequestedMode)); } } } @@ -6601,13 +6601,13 @@ public sealed class Converter : JsonConverter /// public override ElicitationCompletedAction Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, ElicitationCompletedAction value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ElicitationCompletedAction)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ElicitationCompletedAction)); } } } @@ -6665,13 +6665,13 @@ public sealed class Converter : JsonConverter /// public override AutoModeSwitchResponse Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, AutoModeSwitchResponse value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AutoModeSwitchResponse)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AutoModeSwitchResponse)); } } } @@ -6732,13 +6732,13 @@ public sealed class Converter : JsonConverter /// public override ExitPlanModeAction Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, ExitPlanModeAction value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ExitPlanModeAction)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ExitPlanModeAction)); } } } @@ -6808,13 +6808,13 @@ public sealed class Converter : JsonConverter /// public override SkillSource Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, SkillSource value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SkillSource)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SkillSource)); } } } @@ -6875,13 +6875,13 @@ public sealed class Converter : JsonConverter /// public override McpServerSource Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, McpServerSource value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpServerSource)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpServerSource)); } } } @@ -6948,13 +6948,13 @@ public sealed class Converter : JsonConverter /// public override McpServerStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, McpServerStatus value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpServerStatus)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpServerStatus)); } } } @@ -7009,13 +7009,13 @@ public sealed class Converter : JsonConverter /// public override ExtensionsLoadedExtensionSource Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, ExtensionsLoadedExtensionSource value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ExtensionsLoadedExtensionSource)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ExtensionsLoadedExtensionSource)); } } } @@ -7076,13 +7076,13 @@ public sealed class Converter : JsonConverter /// public override ExtensionsLoadedExtensionStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// public override void Write(Utf8JsonWriter writer, ExtensionsLoadedExtensionStatus value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ExtensionsLoadedExtensionStatus)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ExtensionsLoadedExtensionStatus)); } } } diff --git a/dotnet/src/GitHub.Copilot.SDK.csproj b/dotnet/src/GitHub.Copilot.SDK.csproj index 933b51de2..2b69cbb6f 100644 --- a/dotnet/src/GitHub.Copilot.SDK.csproj +++ b/dotnet/src/GitHub.Copilot.SDK.csproj @@ -2,8 +2,9 @@ net8.0;net10.0;netstandard2.0 + GitHub.Copilot true - 0.1.0 + 0.0.0-dev SDK for programmatic control of GitHub Copilot CLI GitHub GitHub diff --git a/dotnet/src/JsonRpc.cs b/dotnet/src/JsonRpc.cs index a97d0baed..866bb868f 100644 --- a/dotnet/src/JsonRpc.cs +++ b/dotnet/src/JsonRpc.cs @@ -14,7 +14,7 @@ using System.Text.Json.Serialization.Metadata; using System.Text.Unicode; -namespace GitHub.Copilot.SDK; +namespace GitHub.Copilot; /// /// A lightweight JSON-RPC 2.0 implementation covering only the features used diff --git a/dotnet/src/LoggingHelpers.cs b/dotnet/src/LoggingHelpers.cs index ca14ea3b9..a4f51cb65 100644 --- a/dotnet/src/LoggingHelpers.cs +++ b/dotnet/src/LoggingHelpers.cs @@ -5,7 +5,7 @@ using Microsoft.Extensions.Logging; using System.Diagnostics; -namespace GitHub.Copilot.SDK; +namespace GitHub.Copilot; internal static class LoggingHelpers { diff --git a/dotnet/src/MillisecondsTimeSpanConverter.cs b/dotnet/src/MillisecondsTimeSpanConverter.cs index 696d053dd..738d68648 100644 --- a/dotnet/src/MillisecondsTimeSpanConverter.cs +++ b/dotnet/src/MillisecondsTimeSpanConverter.cs @@ -6,7 +6,7 @@ using System.Text.Json; using System.Text.Json.Serialization; -namespace GitHub.Copilot.SDK; +namespace GitHub.Copilot; /// Converts between JSON numeric milliseconds and . [EditorBrowsable(EditorBrowsableState.Never)] diff --git a/dotnet/src/PermissionHandlers.cs b/dotnet/src/PermissionHandlers.cs index 3a40e7244..0e4af7eac 100644 --- a/dotnet/src/PermissionHandlers.cs +++ b/dotnet/src/PermissionHandlers.cs @@ -2,12 +2,12 @@ * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ -namespace GitHub.Copilot.SDK; +namespace GitHub.Copilot; -/// Provides pre-built implementations. +/// Provides pre-built permission request handlers. public static class PermissionHandler { - /// A that approves all permission requests. - public static PermissionRequestHandler ApproveAll { get; } = + /// A permission handler that approves all permission requests. + public static Func> ApproveAll { get; } = (_, _) => Task.FromResult(new PermissionRequestResult { Kind = PermissionRequestResultKind.Approved }); } diff --git a/dotnet/src/SdkProtocolVersion.cs b/dotnet/src/SdkProtocolVersion.cs index 889af460b..659387b79 100644 --- a/dotnet/src/SdkProtocolVersion.cs +++ b/dotnet/src/SdkProtocolVersion.cs @@ -1,6 +1,6 @@ // Code generated by update-protocol-version.ts. DO NOT EDIT. -namespace GitHub.Copilot.SDK; +namespace GitHub.Copilot; /// /// Provides the SDK protocol version. diff --git a/dotnet/src/Session.cs b/dotnet/src/Session.cs index 1ca9eb621..fc7d82675 100644 --- a/dotnet/src/Session.cs +++ b/dotnet/src/Session.cs @@ -1,8 +1,8 @@ -/*--------------------------------------------------------------------------------------------- +/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ -using GitHub.Copilot.SDK.Rpc; +using GitHub.Copilot.Rpc; using Microsoft.Extensions.AI; using Microsoft.Extensions.Logging; using System.Collections.Immutable; @@ -12,7 +12,7 @@ using System.Text.Json.Serialization; using System.Threading.Channels; -namespace GitHub.Copilot.SDK; +namespace GitHub.Copilot; /// /// Represents a single conversation session with the Copilot CLI. @@ -41,7 +41,7 @@ namespace GitHub.Copilot.SDK; /// await using var session = await client.CreateSessionAsync(new() { OnPermissionRequest = PermissionHandler.ApproveAll, Model = "gpt-4" }); /// /// // Subscribe to events -/// using var subscription = session.On(evt => +/// using var subscription = session.On<SessionEvent>(evt => /// { /// if (evt is AssistantMessageEvent assistantMessage) /// { @@ -56,16 +56,18 @@ namespace GitHub.Copilot.SDK; public sealed partial class CopilotSession : IAsyncDisposable { private readonly Dictionary _toolHandlers = []; - private readonly Dictionary _commandHandlers = []; + private readonly Dictionary> _commandHandlers = []; private readonly ILogger _logger; private readonly CopilotClient _parentClient; - private volatile PermissionRequestHandler? _permissionHandler; - private volatile UserInputHandler? _userInputHandler; - private volatile ElicitationHandler? _elicitationHandler; - private volatile ExitPlanModeHandler? _exitPlanModeHandler; - private volatile AutoModeSwitchHandler? _autoModeSwitchHandler; - private ImmutableArray _eventHandlers = ImmutableArray.Empty; + private volatile Func>? _permissionHandler; + private volatile Func>? _userInputHandler; + private volatile Func>? _elicitationHandler; + private volatile Func>? _exitPlanModeHandler; + private volatile Func>? _autoModeSwitchHandler; + private ImmutableArray _eventHandlers = ImmutableArray.Empty; + + private sealed record EventSubscription(Type EventType, Action Handler); private SessionHooks? _hooks; private readonly SemaphoreSlim _hooksLock = new(1, 1); @@ -189,7 +191,34 @@ private Task InvokeRpcAsync(string method, object?[]? args, CancellationTo } /// - /// Sends a message to the Copilot session and waits for the response. + /// Sends a plain-text user message and returns the message ID without waiting for + /// the assistant to reply. Convenience overload for . + /// + /// The user message text. + /// A that can be used to cancel the operation. + /// A task that resolves with the message ID. + public Task SendAsync(string prompt, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(prompt); + return SendAsync(new MessageOptions { Prompt = prompt }, cancellationToken); + } + + /// + /// Sends a plain-text user message and waits until the session becomes idle. + /// Convenience overload for . + /// + /// The user message text. + /// Timeout duration (default: 60 seconds). + /// A that can be used to cancel the operation. + /// A task that resolves with the final assistant message event, or null if none was received. + public Task SendAndWaitAsync(string prompt, TimeSpan? timeout = null, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(prompt); + return SendAndWaitAsync(new MessageOptions { Prompt = prompt }, timeout, cancellationToken); + } + + /// + /// Sends a message to the Copilot session. /// /// Options for the message to be sent, including the prompt and optional attachments. /// A that can be used to cancel the operation. @@ -197,11 +226,11 @@ private Task InvokeRpcAsync(string method, object?[]? args, CancellationTo /// Thrown if the session has been disposed. /// /// - /// This method returns immediately after the message is queued. Use + /// This method returns immediately after the message is queued. Use /// if you need to wait for the assistant to finish processing. /// /// - /// Subscribe to events via to receive streaming responses and other session events. + /// Subscribe to events via to receive streaming responses and other session events. /// /// /// @@ -258,12 +287,12 @@ public async Task SendAsync(MessageOptions options, CancellationToken ca /// Thrown if the session has been disposed. /// /// - /// This is a convenience method that combines with waiting for + /// This is a convenience method that combines with waiting for /// the session.idle event. Use this when you want to block until the assistant /// has finished processing the message. /// /// - /// Events are still delivered to handlers registered via while waiting. + /// Events are still delivered to handlers registered via while waiting. /// /// /// @@ -318,7 +347,7 @@ void Handler(SessionEvent evt) } } - using var subscription = On(Handler); + using var subscription = On(Handler); await SendAsync(options, cancellationToken); @@ -381,7 +410,7 @@ void Handler(SessionEvent evt) /// /// /// - /// using var subscription = session.On(evt => + /// using var subscription = session.On<SessionEvent>(evt => /// { /// switch (evt) /// { @@ -394,16 +423,21 @@ void Handler(SessionEvent evt) /// } /// }); /// + /// // Or filter to a specific event kind at compile time: + /// using var sub2 = session.On<AssistantMessageEvent>(evt => + /// Console.WriteLine(evt.Data?.Content)); + /// /// // The handler is automatically unsubscribed when the subscription is disposed. /// /// - public IDisposable On(SessionEventHandler handler) + public IDisposable On(Action handler) where T : SessionEvent { ArgumentNullException.ThrowIfNull(handler); ThrowIfDisposed(); - ImmutableInterlocked.Update(ref _eventHandlers, array => array.Add(handler)); - return new ActionDisposable(() => ImmutableInterlocked.Update(ref _eventHandlers, array => array.Remove(handler))); + var subscription = new EventSubscription(typeof(T), evt => handler((T)evt)); + ImmutableInterlocked.Update(ref _eventHandlers, array => array.Add(subscription)); + return new ActionDisposable(() => ImmutableInterlocked.Update(ref _eventHandlers, array => array.Remove(subscription))); } /// @@ -438,11 +472,16 @@ private async Task ProcessEventsAsync() await foreach (var sessionEvent in _eventChannel.Reader.ReadAllAsync()) { var dispatchTimestamp = Stopwatch.GetTimestamp(); - foreach (var handler in _eventHandlers) + var eventType = sessionEvent.GetType(); + foreach (var subscription in _eventHandlers) { + if (!subscription.EventType.IsAssignableFrom(eventType)) + { + continue; + } try { - handler(sessionEvent); + subscription.Handler(sessionEvent); } catch (Exception ex) { @@ -496,7 +535,7 @@ internal void RegisterTools(ICollection tools) /// When the assistant needs permission to perform certain actions (e.g., file operations), /// this handler is called to approve or deny the request. /// - internal void RegisterPermissionHandler(PermissionRequestHandler? handler) + internal void RegisterPermissionHandler(Func>? handler) { _permissionHandler = handler; } @@ -726,7 +765,7 @@ private async Task ExecuteToolAndRespondAsync(string requestId, string toolName, /// /// Executes a permission handler and sends the result back via the HandlePendingPermissionRequest RPC. /// - private async Task ExecutePermissionAndRespondAsync(string requestId, PermissionRequest permissionRequest, PermissionRequestHandler handler) + private async Task ExecutePermissionAndRespondAsync(string requestId, PermissionRequest permissionRequest, Func> handler) { try { @@ -747,7 +786,10 @@ private async Task ExecutePermissionAndRespondAsync(string requestId, Permission return; } var responseRpcTimestamp = Stopwatch.GetTimestamp(); - await Rpc.Permissions.HandlePendingPermissionRequestAsync(requestId, new PermissionDecision { Kind = result.Kind.Value }); + PermissionDecision decision = result.Kind == PermissionRequestResultKind.Rejected + ? new PermissionDecisionReject { Feedback = result.Feedback } + : new PermissionDecision { Kind = result.Kind.Value }; + await Rpc.Permissions.HandlePendingPermissionRequestAsync(requestId, decision); LoggingHelpers.LogTiming(_logger, LogLevel.Debug, null, "CopilotSession.ExecutePermissionAndRespondAsync response sent successfully. Elapsed={Elapsed}, SessionId={SessionId}, RequestId={RequestId}", responseRpcTimestamp, @@ -778,7 +820,7 @@ private async Task ExecutePermissionAndRespondAsync(string requestId, Permission /// Registers a handler for user input requests from the agent. /// /// The handler to invoke when user input is requested. - internal void RegisterUserInputHandler(UserInputHandler handler) + internal void RegisterUserInputHandler(Func> handler) { _userInputHandler = handler; } @@ -801,7 +843,7 @@ internal void RegisterCommands(IEnumerable? commands) /// Registers an elicitation handler for this session. /// /// The handler to invoke when an elicitation request is received. - internal void RegisterElicitationHandler(ElicitationHandler? handler) + internal void RegisterElicitationHandler(Func>? handler) { _elicitationHandler = handler; } @@ -810,7 +852,7 @@ internal void RegisterElicitationHandler(ElicitationHandler? handler) /// Registers an exit-plan-mode handler for this session. /// /// The handler to invoke when an exit-plan-mode request is received. - internal void RegisterExitPlanModeHandler(ExitPlanModeHandler? handler) + internal void RegisterExitPlanModeHandler(Func>? handler) { _exitPlanModeHandler = handler; } @@ -819,7 +861,7 @@ internal void RegisterExitPlanModeHandler(ExitPlanModeHandler? handler) /// Registers an auto-mode-switch handler for this session. /// /// The handler to invoke when an auto-mode-switch request is received. - internal void RegisterAutoModeSwitchHandler(AutoModeSwitchHandler? handler) + internal void RegisterAutoModeSwitchHandler(Func>? handler) { _autoModeSwitchHandler = handler; } @@ -958,7 +1000,7 @@ private void AssertElicitation() /// private sealed class SessionUiApiImpl(CopilotSession session) : ISessionUiApi { - public async Task ElicitationAsync(ElicitationParams elicitationParams, CancellationToken cancellationToken) + public async Task ElicitAsync(ElicitationParams elicitationParams, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(elicitationParams); session.ThrowIfDisposed(); @@ -1041,7 +1083,7 @@ public async Task ConfirmAsync(string message, CancellationToken cancellat return null; } - public async Task InputAsync(string message, InputOptions? options, CancellationToken cancellationToken) + public async Task InputAsync(string message, UiInputOptions? options, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(message); session.ThrowIfDisposed(); @@ -1319,7 +1361,7 @@ internal async Task HandleSystemMessageTransf /// /// /// - /// var events = await session.GetMessagesAsync(); + /// var events = await session.GetEventsAsync(); /// foreach (var evt in events) /// { /// if (evt is AssistantMessageEvent) @@ -1329,7 +1371,7 @@ internal async Task HandleSystemMessageTransf /// } /// /// - public async Task> GetMessagesAsync(CancellationToken cancellationToken = default) + public async Task> GetEventsAsync(CancellationToken cancellationToken = default) { ThrowIfDisposed(); @@ -1437,7 +1479,7 @@ public async Task LogAsync(string message, SessionLogLevel? level = null, bool? /// A task representing the dispose operation. /// /// - /// The caller should ensure the session is idle (e.g., + /// The caller should ensure the session is idle (e.g., /// has returned) before disposing. If the session is not idle, in-flight event handlers /// or tool handlers may observe failures. /// @@ -1491,7 +1533,7 @@ await InvokeRpcAsync( GC.SuppressFinalize(this); } - _eventHandlers = ImmutableInterlocked.InterlockedExchange(ref _eventHandlers, ImmutableArray.Empty); + _eventHandlers = ImmutableInterlocked.InterlockedExchange(ref _eventHandlers, ImmutableArray.Empty); _toolHandlers.Clear(); _commandHandlers.Clear(); diff --git a/dotnet/src/SessionFsProvider.cs b/dotnet/src/SessionFsProvider.cs index 3e3638c83..12dfc8770 100644 --- a/dotnet/src/SessionFsProvider.cs +++ b/dotnet/src/SessionFsProvider.cs @@ -1,17 +1,17 @@ -/*--------------------------------------------------------------------------------------------- +/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ -using GitHub.Copilot.SDK.Rpc; +using GitHub.Copilot.Rpc; -namespace GitHub.Copilot.SDK; +namespace GitHub.Copilot; /// /// Result of a SQLite query execution via . /// Same shape as but without the Error field, /// since providers signal errors by throwing. /// -public class SessionFsSqliteResult +public sealed class SessionFsSqliteResult { /// Column names from the result set. public IList Columns { get; set; } = []; @@ -99,24 +99,24 @@ public abstract class SessionFsProvider : ISessionFsHandler /// Whether to create parent directories. /// Optional POSIX-style permission mode (e.g., 0x1FF for 0777). Null means use OS default. /// Cancellation token. - protected abstract Task MkdirAsync(string path, bool recursive, int? mode, CancellationToken cancellationToken); + protected abstract Task MakeDirectoryAsync(string path, bool recursive, int? mode, CancellationToken cancellationToken); /// Lists entry names in a directory. Throw if the directory does not exist. /// SessionFs-relative path. /// Cancellation token. - protected abstract Task> ReaddirAsync(string path, CancellationToken cancellationToken); + protected abstract Task> ReadDirectoryAsync(string path, CancellationToken cancellationToken); /// Lists entries with type info in a directory. Throw if the directory does not exist. /// SessionFs-relative path. /// Cancellation token. - protected abstract Task> ReaddirWithTypesAsync(string path, CancellationToken cancellationToken); + protected abstract Task> ReadDirectoryWithTypesAsync(string path, CancellationToken cancellationToken); /// Removes a file or directory. Throw if the path does not exist (unless is true). /// SessionFs-relative path. /// Whether to remove directory contents recursively. /// If true, do not throw when the path does not exist. /// Cancellation token. - protected abstract Task RmAsync(string path, bool recursive, bool force, CancellationToken cancellationToken); + protected abstract Task RemoveAsync(string path, bool recursive, bool force, CancellationToken cancellationToken); /// Renames/moves a file or directory. /// Source path. @@ -206,7 +206,7 @@ async Task ISessionFsHandler.StatAsync(SessionFsStatRequest try { - await MkdirAsync(request.Path, request.Recursive ?? false, (int?)request.Mode, cancellationToken).ConfigureAwait(false); + await MakeDirectoryAsync(request.Path, request.Recursive ?? false, (int?)request.Mode, cancellationToken).ConfigureAwait(false); return null; } catch (Exception ex) @@ -221,7 +221,7 @@ async Task ISessionFsHandler.ReaddirAsync(SessionFsReadd try { - var entries = await ReaddirAsync(request.Path, cancellationToken).ConfigureAwait(false); + var entries = await ReadDirectoryAsync(request.Path, cancellationToken).ConfigureAwait(false); return new SessionFsReaddirResult { Entries = entries }; } catch (Exception ex) @@ -236,7 +236,7 @@ async Task ISessionFsHandler.ReaddirWithTypesAs try { - var entries = await ReaddirWithTypesAsync(request.Path, cancellationToken).ConfigureAwait(false); + var entries = await ReadDirectoryWithTypesAsync(request.Path, cancellationToken).ConfigureAwait(false); return new SessionFsReaddirWithTypesResult { Entries = entries }; } catch (Exception ex) @@ -251,7 +251,7 @@ async Task ISessionFsHandler.ReaddirWithTypesAs try { - await RmAsync(request.Path, request.Recursive ?? false, request.Force ?? false, cancellationToken).ConfigureAwait(false); + await RemoveAsync(request.Path, request.Recursive ?? false, request.Force ?? false, cancellationToken).ConfigureAwait(false); return null; } catch (Exception ex) diff --git a/dotnet/src/Telemetry.cs b/dotnet/src/Telemetry.cs index 6bae267a9..893992e10 100644 --- a/dotnet/src/Telemetry.cs +++ b/dotnet/src/Telemetry.cs @@ -4,7 +4,7 @@ using System.Diagnostics; -namespace GitHub.Copilot.SDK; +namespace GitHub.Copilot; internal static class TelemetryHelpers { diff --git a/dotnet/src/Types.cs b/dotnet/src/Types.cs index 99e2f142d..9cc070f78 100644 --- a/dotnet/src/Types.cs +++ b/dotnet/src/Types.cs @@ -1,8 +1,8 @@ -/*--------------------------------------------------------------------------------------------- +/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ -using GitHub.Copilot.SDK.Rpc; +using GitHub.Copilot.Rpc; using Microsoft.Extensions.AI; using Microsoft.Extensions.Logging; using System.ComponentModel; @@ -11,7 +11,7 @@ using System.Text.Json; using System.Text.Json.Serialization; -namespace GitHub.Copilot.SDK; +namespace GitHub.Copilot; internal static class GeneratedStringEnumJson { @@ -50,29 +50,166 @@ internal static class Diagnostics } /// -/// Represents the connection state of the Copilot client. +/// Log level for the Copilot runtime. Use the well-known values exposed as +/// static members (, , , +/// , , ), or construct +/// your own with if the runtime accepts +/// additional values. The runtime does not necessarily treat the values as a +/// linear scale, so do not assume </> comparisons are meaningful. /// -[JsonConverter(typeof(JsonStringEnumConverter))] -public enum ConnectionState +[DebuggerDisplay("{Value,nq}")] +public readonly struct CopilotLogLevel : IEquatable +{ + /// Disable logging entirely. + public static CopilotLogLevel None { get; } = new("none"); + + /// Log only errors. + public static CopilotLogLevel Error { get; } = new("error"); + + /// Log warnings and errors. + public static CopilotLogLevel Warning { get; } = new("warning"); + + /// Log informational messages, warnings, and errors. + public static CopilotLogLevel Info { get; } = new("info"); + + /// Log debug-level diagnostics in addition to the above. + public static CopilotLogLevel Debug { get; } = new("debug"); + + /// Log every diagnostic the runtime emits. + public static CopilotLogLevel All { get; } = new("all"); + + /// Gets the underlying string value of this . + public string Value => _value ?? string.Empty; + + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The wire string value for this log level. + public CopilotLogLevel(string value) => _value = value; + + /// + public static bool operator ==(CopilotLogLevel left, CopilotLogLevel right) => left.Equals(right); + + /// + public static bool operator !=(CopilotLogLevel left, CopilotLogLevel right) => !left.Equals(right); + + /// + public override bool Equals([NotNullWhen(true)] object? obj) => obj is CopilotLogLevel other && Equals(other); + + /// + public bool Equals(CopilotLogLevel other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + + /// + public override string ToString() => Value; +} + +/// +/// Configures how a connects to the Copilot runtime. +/// Use the factory methods on this class to construct an instance. +/// +public abstract class RuntimeConnection +{ + internal RuntimeConnection() { } + + /// + /// Spawn a runtime child process and communicate over its stdin/stdout. + /// This is the default if no is set. + /// + /// Path to the runtime executable. When null, the bundled runtime is used. + /// Extra command-line arguments to pass to the runtime process. + public static StdioRuntimeConnection ForStdio(string? path = null, IList? args = null) + => new() { Path = path, Args = args }; + + /// + /// Spawn a runtime child process that listens on a TCP socket and connect to it. + /// + /// TCP port to listen on. 0 (the default) auto-allocates a free port. + /// If the chosen port is already in use, startup fails. + /// Optional shared secret the SDK sends to the spawned runtime to authenticate the TCP connection. + /// When null, a GUID is generated automatically. + /// Path to the runtime executable. When null, the bundled runtime is used. + /// Extra command-line arguments to pass to the runtime process. + public static TcpRuntimeConnection ForTcp(int port = 0, string? connectionToken = null, string? path = null, IList? args = null) + => new() { Port = port, ConnectionToken = connectionToken, Path = path, Args = args }; + + /// + /// Connect to an already-running runtime at a given URI. + /// + /// URL of the runtime to connect to. Accepts "port", "host:port", or a full URL. + /// Optional shared secret to authenticate the connection. + public static UriRuntimeConnection ForUri(string url, string? connectionToken = null) + => new() { Url = url, ConnectionToken = connectionToken }; +} + +/// +/// Base for kinds that spawn a runtime child process. +/// +public abstract class ChildProcessRuntimeConnection : RuntimeConnection +{ + internal ChildProcessRuntimeConnection() { } + + /// Path to the runtime executable. When null, the bundled runtime is used. + public string? Path { get; set; } + + /// Extra command-line arguments to pass to the runtime process. + public IList? Args { get; set; } +} + +/// +/// Spawns a runtime child process and communicates over stdin/stdout. Construct via +/// . +/// +public sealed class StdioRuntimeConnection : ChildProcessRuntimeConnection +{ + internal StdioRuntimeConnection() { } +} + +/// +/// Spawns a runtime child process listening on a TCP socket. Construct via +/// . +/// +public sealed class TcpRuntimeConnection : ChildProcessRuntimeConnection +{ + internal TcpRuntimeConnection() { } + + /// + /// TCP port to listen on. 0 (the default) auto-allocates a free port. + /// If the chosen port is already in use, startup fails. + /// + public int Port { get; set; } + + /// + /// Optional shared secret the SDK sends to the spawned runtime to authenticate + /// the TCP connection. When null, a GUID is generated automatically. + /// + public string? ConnectionToken { get; set; } +} + +/// +/// Connects to an already-running runtime at the specified URL. Construct via +/// . +/// +public sealed class UriRuntimeConnection : RuntimeConnection { - /// The client is not connected to the server. - [JsonStringEnumMemberName("disconnected")] - Disconnected, - /// The client is establishing a connection to the server. - [JsonStringEnumMemberName("connecting")] - Connecting, - /// The client is connected and ready to communicate. - [JsonStringEnumMemberName("connected")] - Connected, - /// The connection is in an error state. - [JsonStringEnumMemberName("error")] - Error + internal UriRuntimeConnection() { } + + /// + /// URL of the runtime to connect to. Accepts "port", "host:port", + /// or a full URL. + /// + public required string Url { get; set; } + + /// Optional shared secret to authenticate the connection. + public string? ConnectionToken { get; set; } } /// /// Configuration options for creating a instance. /// -public class CopilotClientOptions +public sealed class CopilotClientOptions { /// /// Initializes a new instance of the class. @@ -83,113 +220,71 @@ public CopilotClientOptions() { } /// Initializes a new instance of the class /// by copying the properties of the specified instance. /// - protected CopilotClientOptions(CopilotClientOptions? other) + private CopilotClientOptions(CopilotClientOptions? other) { if (other is null) return; - AutoStart = other.AutoStart; -#pragma warning disable CS0618 // Obsolete member - AutoRestart = other.AutoRestart; -#pragma warning restore CS0618 - CliArgs = (string[]?)other.CliArgs?.Clone(); - CliPath = other.CliPath; - CliUrl = other.CliUrl; - Cwd = other.Cwd; - CopilotHome = other.CopilotHome; + Connection = other.Connection; + WorkingDirectory = other.WorkingDirectory; + BaseDirectory = other.BaseDirectory; Environment = other.Environment; GitHubToken = other.GitHubToken; Logger = other.Logger; LogLevel = other.LogLevel; - Port = other.Port; Telemetry = other.Telemetry; UseLoggedInUser = other.UseLoggedInUser; - UseStdio = other.UseStdio; OnListModels = other.OnListModels; SessionFs = other.SessionFs; SessionIdleTimeoutSeconds = other.SessionIdleTimeoutSeconds; - TcpConnectionToken = other.TcpConnectionToken; - Remote = other.Remote; + EnableRemoteSessions = other.EnableRemoteSessions; } /// - /// Path to the Copilot CLI executable. If not specified, uses the bundled CLI from the SDK. - /// - public string? CliPath { get; set; } - /// - /// Additional command-line arguments to pass to the CLI process. + /// How to connect to the runtime. When null, the default is + /// with the bundled runtime. /// - public string[]? CliArgs { get; set; } + public RuntimeConnection? Connection { get; set; } + /// - /// Working directory for the CLI process. + /// Working directory for the runtime process. /// - public string? Cwd { get; set; } + public string? WorkingDirectory { get; set; } + /// /// Base directory for Copilot data (session state, config, etc.). - /// Sets the COPILOT_HOME environment variable on the spawned CLI process. - /// When , the CLI defaults to ~/.copilot. - /// This option is only used when the SDK spawns the CLI process; it is ignored - /// when connecting to an external server via . - /// - public string? CopilotHome { get; set; } - /// - /// Port number for the CLI server when not using stdio transport. - /// - public int Port { get; set; } - /// - /// Whether to use stdio transport for communication with the CLI server. - /// Defaults to true when neither nor - /// switches the client into TCP mode. Setting this to true is mutually - /// exclusive with . - /// - public bool? UseStdio { get; set; } - /// - /// URL of an existing CLI server to connect to instead of starting a new one. - /// - public string? CliUrl { get; set; } - /// - /// Log level for the CLI server (e.g., "info", "debug", "warn", "error"). + /// Sets the COPILOT_HOME environment variable on the spawned runtime. + /// When , the runtime defaults to ~/.copilot. + /// Ignored when connecting to an existing runtime via + /// . /// - public string LogLevel { get; set; } = "info"; - /// - /// Whether to automatically start the CLI server if it is not already running. - /// - public bool AutoStart { get; set; } = true; - /// - /// Obsolete. This option has no effect. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("AutoRestart has no effect and will be removed in a future release.")] - public bool AutoRestart { get; set; } + public string? BaseDirectory { get; set; } + /// - /// Environment variables to pass to the CLI process. + /// Log level for the Copilot runtime. Use the well-known values on + /// (, + /// , , + /// , , + /// ). When null, the runtime's default + /// log level is used. /// + public CopilotLogLevel? LogLevel { get; set; } + + /// Environment variables to pass to the runtime process. public IReadOnlyDictionary? Environment { get; set; } - /// - /// Logger instance for SDK diagnostic output. - /// + + /// Logger instance for SDK diagnostic output. public ILogger? Logger { get; set; } /// /// GitHub token to use for authentication. - /// When provided, the token is passed to the CLI server via environment variable. + /// When provided, the token is passed to the runtime via environment variable. /// This takes priority over other authentication methods. /// public string? GitHubToken { get; set; } - /// - /// Obsolete. Use instead. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("Use GitHubToken instead.", error: false)] - public string? GithubToken - { - get => GitHubToken; - set => GitHubToken = value; - } - /// /// Whether to use the logged-in user for authentication. - /// When true, the CLI server will attempt to use stored OAuth tokens or gh CLI auth. + /// When true, the runtime will attempt to use stored OAuth tokens or gh CLI auth. /// When false, only explicit tokens (GitHubToken or environment variables) are used. /// Default: true (but defaults to false when GitHubToken is provided). /// @@ -198,7 +293,7 @@ public string? GithubToken /// /// Custom handler for listing available models. /// When provided, ListModelsAsync() calls this handler instead of - /// querying the CLI server. Useful in BYOK mode to return models + /// querying the runtime. Useful in BYOK mode to return models /// available from your custom provider. /// public Func>>? OnListModels { get; set; } @@ -207,13 +302,13 @@ public string? GithubToken /// Custom session filesystem provider configuration. /// When set, the client registers as the session filesystem provider on connect, /// routing session-scoped file I/O through per-session handlers created via - /// or . + /// . /// public SessionFsConfig? SessionFs { get; set; } /// - /// OpenTelemetry configuration for the CLI server. - /// When set to a non- instance, the CLI server is started with OpenTelemetry instrumentation enabled. + /// OpenTelemetry configuration for the runtime. + /// When set to a non- instance, the runtime is started with OpenTelemetry instrumentation enabled. /// public TelemetryConfig? Telemetry { get; set; } @@ -221,26 +316,19 @@ public string? GithubToken /// Server-wide idle timeout for sessions in seconds. /// Sessions without activity for this duration are automatically cleaned up. /// Set to 0 or leave as to disable (sessions live indefinitely). - /// This option is only used when the SDK spawns the CLI process; it is ignored - /// when connecting to an external server via . + /// This option is only used when the SDK spawns the runtime; it is ignored + /// when connecting to an external runtime via . /// public int? SessionIdleTimeoutSeconds { get; set; } - /// - /// Connection token for the headless CLI server (TCP only). When the SDK spawns its own - /// CLI in TCP mode and this is omitted, a GUID is generated automatically so the loopback - /// listener is safe by default. Cannot be combined with = true. - /// - public string? TcpConnectionToken { get; set; } - /// /// Enable remote session support (Mission Control integration). /// When true, sessions in a GitHub repository working directory are /// accessible from GitHub web and mobile. - /// This option is only used when the SDK spawns the CLI process; it is ignored - /// when connecting to an external server via . + /// This option is only used when the SDK spawns the runtime; it is ignored + /// when connecting to an external runtime via . /// - public bool Remote { get; set; } + public bool EnableRemoteSessions { get; set; } /// /// Creates a shallow clone of this instance. @@ -251,10 +339,7 @@ public string? GithubToken /// Other reference-type properties (for example delegates and the logger) are not /// deep-cloned; the original and the clone will share those objects. /// - public virtual CopilotClientOptions Clone() - { - return new(this); - } + public CopilotClientOptions Clone() => new(this); } /// @@ -335,7 +420,7 @@ public sealed class SessionFsConfig /// /// Represents a binary result returned by a tool invocation. /// -public class ToolBinaryResult +public sealed class ToolBinaryResult { /// /// Base64-encoded binary data. @@ -350,10 +435,11 @@ public class ToolBinaryResult public string MimeType { get; set; } = string.Empty; /// - /// Type identifier for the binary result. + /// Type identifier for the binary result. Use the well-known values on + /// ("image", "resource"). /// [JsonPropertyName("type")] - public string Type { get; set; } = string.Empty; + public ToolBinaryResultType Type { get; set; } /// /// Optional human-readable description of the binary result. @@ -362,10 +448,76 @@ public class ToolBinaryResult public string? Description { get; set; } } +/// Describes the kind of a . +[JsonConverter(typeof(ToolBinaryResultType.Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct ToolBinaryResultType : IEquatable +{ + /// Gets the kind indicating an inline image result. + public static ToolBinaryResultType Image { get; } = new("image"); + + /// Gets the kind indicating an MCP resource result. + public static ToolBinaryResultType Resource { get; } = new("resource"); + + /// Gets the underlying string value of this . + public string Value => _value ?? string.Empty; + + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The string value for this type. + [JsonConstructor] + public ToolBinaryResultType(string value) => _value = value; + + /// + public static bool operator ==(ToolBinaryResultType left, ToolBinaryResultType right) => left.Equals(right); + + /// + public static bool operator !=(ToolBinaryResultType left, ToolBinaryResultType right) => !left.Equals(right); + + /// + public override bool Equals([NotNullWhen(true)] object? obj) => obj is ToolBinaryResultType other && Equals(other); + + /// + public bool Equals(ToolBinaryResultType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + + /// + public override string ToString() => Value; + + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override ToolBinaryResultType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType != JsonTokenType.String) + { + throw new JsonException("Expected string for ToolBinaryResultType."); + } + + var value = reader.GetString(); + if (value is null) + { + throw new JsonException("ToolBinaryResultType value cannot be null."); + } + + return new ToolBinaryResultType(value); + } + + /// + public override void Write(Utf8JsonWriter writer, ToolBinaryResultType value, JsonSerializerOptions options) => + writer.WriteStringValue(value.Value); + } +} + /// /// Represents the structured result of a tool execution. /// -public class ToolResultObject +public sealed class ToolResultObject { /// /// Text result to be consumed by the language model. @@ -477,7 +629,7 @@ private static ToolResultObject ConvertAIContents(IEnumerable content { Data = dataContent.Base64Data.ToString(), MimeType = dataContent.MediaType ?? "application/octet-stream", - Type = dataContent.HasTopLevelMediaType("image") ? "image" : "resource", + Type = dataContent.HasTopLevelMediaType("image") ? ToolBinaryResultType.Image : ToolBinaryResultType.Resource, }); break; @@ -502,7 +654,7 @@ private static string SerializeAIContent(AIContent content) => /// /// Contains context for a tool invocation callback. /// -public class ToolInvocation +public sealed class ToolInvocation { /// /// Identifier of the session that triggered the tool call. @@ -522,11 +674,6 @@ public class ToolInvocation public object? Arguments { get; set; } } -/// -/// Delegate for handling tool invocations and returning a result. -/// -public delegate Task ToolHandler(ToolInvocation invocation); - /// Describes the kind of a permission request result. [JsonConverter(typeof(PermissionRequestResultKind.Converter))] [DebuggerDisplay("{Value,nq}")] @@ -544,21 +691,6 @@ public class ToolInvocation /// Gets the kind indicating no permission decision was made. public static PermissionRequestResultKind NoResult { get; } = new("no-result"); - /// Deprecated. Use instead. - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("Use Rejected instead.")] - public static PermissionRequestResultKind DeniedInteractivelyByUser => Rejected; - - /// Deprecated. Use instead. - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("Use UserNotAvailable instead.")] - public static PermissionRequestResultKind DeniedCouldNotRequestFromUser => UserNotAvailable; - - /// Deprecated. Use instead. - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("Use UserNotAvailable instead.")] - public static PermissionRequestResultKind DeniedByRules => UserNotAvailable; - /// Gets the underlying string value of this . public string Value => _value ?? string.Empty; @@ -617,16 +749,16 @@ public override void Write(Utf8JsonWriter writer, PermissionRequestResultKind va /// /// Result of a permission request evaluation. /// -public class PermissionRequestResult +public sealed class PermissionRequestResult { /// - /// Permission decision kind. Use the static members of - /// to construct values. Valid kinds are: + /// Permission decision kind. Construct values with the static members on + /// : /// - /// "approve-once" () — allow this single request. - /// "reject" () — deny the request. - /// "user-not-available" () — deny because no user is available to confirm. - /// "no-result" () — leave the pending request unanswered (protocol v1 only; rejected by protocol v2 servers). + /// — allow this single request. + /// — deny the request. + /// — deny because no user is available to confirm. + /// — leave the pending request unanswered (protocol v1 only; rejected by protocol v2 servers). /// /// [JsonPropertyName("kind")] @@ -637,12 +769,20 @@ public class PermissionRequestResult /// [JsonPropertyName("rules")] public IList? Rules { get; set; } + + /// + /// Optional human-readable feedback to forward to the LLM along with the + /// decision. Mirrors the feedback field on the RPC-level + /// type. + /// + [JsonPropertyName("feedback")] + public string? Feedback { get; set; } } /// /// Contains context for a permission request callback. /// -public class PermissionInvocation +public sealed class PermissionInvocation { /// /// Identifier of the session that triggered the permission request. @@ -650,11 +790,6 @@ public class PermissionInvocation public string SessionId { get; set; } = string.Empty; } -/// -/// Delegate for handling permission requests and returning a decision. -/// -public delegate Task PermissionRequestHandler(PermissionRequest request, PermissionInvocation invocation); - // ============================================================================ // User Input Handler Types // ============================================================================ @@ -662,7 +797,7 @@ public class PermissionInvocation /// /// Request for user input from the agent. /// -public class UserInputRequest +public sealed class UserInputRequest { /// /// The question to ask the user. @@ -686,7 +821,7 @@ public class UserInputRequest /// /// Response to a user input request. /// -public class UserInputResponse +public sealed class UserInputResponse { /// /// The user's answer. @@ -704,7 +839,7 @@ public class UserInputResponse /// /// Context for a user input request invocation. /// -public class UserInputInvocation +public sealed class UserInputInvocation { /// /// Identifier of the session that triggered the user input request. @@ -712,15 +847,10 @@ public class UserInputInvocation public string SessionId { get; set; } = string.Empty; } -/// -/// Handler for user input requests from the agent. -/// -public delegate Task UserInputHandler(UserInputRequest request, UserInputInvocation invocation); - /// /// Request to exit plan mode and continue with a selected action. /// -public class ExitPlanModeRequest +public sealed class ExitPlanModeRequest { /// /// Summary of the plan or proposed next step. @@ -750,7 +880,7 @@ public class ExitPlanModeRequest /// /// Response to an exit-plan-mode request. /// -public class ExitPlanModeResult +public sealed class ExitPlanModeResult { /// /// Whether the user approved exiting plan mode. @@ -774,7 +904,7 @@ public class ExitPlanModeResult /// /// Context for an exit-plan-mode request invocation. /// -public class ExitPlanModeInvocation +public sealed class ExitPlanModeInvocation { /// /// Identifier of the session that triggered the request. @@ -782,15 +912,10 @@ public class ExitPlanModeInvocation public string SessionId { get; set; } = string.Empty; } -/// -/// Handler for exit-plan-mode requests from the agent. -/// -public delegate Task ExitPlanModeHandler(ExitPlanModeRequest request, ExitPlanModeInvocation invocation); - /// /// Request to switch to auto mode after an eligible rate limit. /// -public class AutoModeSwitchRequest +public sealed class AutoModeSwitchRequest { /// /// The rate-limit error code that triggered the request. @@ -808,7 +933,7 @@ public class AutoModeSwitchRequest /// /// Context for an auto-mode-switch request invocation. /// -public class AutoModeSwitchInvocation +public sealed class AutoModeSwitchInvocation { /// /// Identifier of the session that triggered the request. @@ -816,11 +941,6 @@ public class AutoModeSwitchInvocation public string SessionId { get; set; } = string.Empty; } -/// -/// Handler for auto-mode-switch requests from the agent. -/// -public delegate Task AutoModeSwitchHandler(AutoModeSwitchRequest request, AutoModeSwitchInvocation invocation); - // ============================================================================ // Command Handler Types // ============================================================================ @@ -828,7 +948,7 @@ public class AutoModeSwitchInvocation /// /// Defines a slash-command that users can invoke from the CLI TUI. /// -public class CommandDefinition +public sealed class CommandDefinition { /// /// Command name (without leading /). For example, "deploy". @@ -843,13 +963,13 @@ public class CommandDefinition /// /// Handler invoked when the command is executed. /// - public required CommandHandler Handler { get; set; } + public required Func Handler { get; set; } } /// -/// Context passed to a when a command is executed. +/// Context passed to a command handler when a command is executed. /// -public class CommandContext +public sealed class CommandContext { /// /// Session ID where the command was invoked. @@ -872,11 +992,6 @@ public class CommandContext public string Args { get; set; } = string.Empty; } -/// -/// Delegate for handling slash-command executions. -/// -public delegate Task CommandHandler(CommandContext context); - // ============================================================================ // Elicitation Types (UI — client → server) // ============================================================================ @@ -884,7 +999,7 @@ public class CommandContext /// /// JSON Schema describing the form fields to present for an elicitation dialog. /// -public class ElicitationSchema +public sealed class ElicitationSchema { /// /// Schema type indicator (always "object"). @@ -908,7 +1023,7 @@ public class ElicitationSchema /// /// Parameters for an elicitation request sent from the SDK to the server. /// -public class ElicitationParams +public sealed class ElicitationParams { /// /// Message describing what information is needed from the user. @@ -924,7 +1039,7 @@ public class ElicitationParams /// /// Result returned from an elicitation dialog. /// -public class ElicitationResult +public sealed class ElicitationResult { /// /// User action: "accept" (submitted), "decline" (rejected), or "cancel" (dismissed). @@ -940,7 +1055,7 @@ public class ElicitationResult /// /// Options for the convenience method. /// -public class InputOptions +public sealed class UiInputOptions { /// Title label for the input field. public string? Title { get; set; } @@ -973,7 +1088,7 @@ public interface ISessionUiApi /// Optional cancellation token. /// The with the user's response. /// Thrown if the host does not support elicitation. - Task ElicitationAsync(ElicitationParams elicitationParams, CancellationToken cancellationToken = default); + Task ElicitAsync(ElicitationParams elicitationParams, CancellationToken cancellationToken = default); /// /// Shows a confirmation dialog and returns the user's boolean answer. @@ -1005,7 +1120,7 @@ public interface ISessionUiApi /// Optional cancellation token. /// The entered string, or null if the user declined/cancelled. /// Thrown if the host does not support elicitation. - Task InputAsync(string message, InputOptions? options = null, CancellationToken cancellationToken = default); + Task InputAsync(string message, UiInputOptions? options = null, CancellationToken cancellationToken = default); } // ============================================================================ @@ -1016,7 +1131,7 @@ public interface ISessionUiApi /// Context for an elicitation handler invocation, combining the request data /// with session context. Mirrors the single-argument pattern of . /// -public class ElicitationContext +public sealed class ElicitationContext { /// Identifier of the session that triggered the elicitation request. public string SessionId { get; set; } = string.Empty; @@ -1037,11 +1152,6 @@ public class ElicitationContext public string? Url { get; set; } } -/// -/// Delegate for handling elicitation requests from the server. -/// -public delegate Task ElicitationHandler(ElicitationContext context); - // ============================================================================ // Session Capabilities // ============================================================================ @@ -1049,7 +1159,7 @@ public class ElicitationContext /// /// Represents the capabilities reported by the host for a session. /// -public class SessionCapabilities +public sealed class SessionCapabilities { /// /// UI-related capabilities. @@ -1060,7 +1170,7 @@ public class SessionCapabilities /// /// UI-specific capability flags for a session. /// -public class SessionUiCapabilities +public sealed class SessionUiCapabilities { /// /// Whether the host supports interactive elicitation dialogs. @@ -1075,7 +1185,7 @@ public class SessionUiCapabilities /// /// Context for a hook invocation. /// -public class HookInvocation +public sealed class HookInvocation { /// /// Identifier of the session that triggered the hook. @@ -1086,7 +1196,7 @@ public class HookInvocation /// /// Input for a pre-tool-use hook. /// -public class PreToolUseHookInput +public sealed class PreToolUseHookInput { /// /// The runtime session ID of the session that triggered the hook. @@ -1098,7 +1208,8 @@ public class PreToolUseHookInput /// Unix timestamp in milliseconds when the tool use was initiated. /// [JsonPropertyName("timestamp")] - public long Timestamp { get; set; } + [JsonConverter(typeof(UnixMillisecondsDateTimeOffsetConverter))] + public DateTimeOffset Timestamp { get; set; } /// /// Current working directory of the session. @@ -1122,7 +1233,7 @@ public class PreToolUseHookInput /// /// Output for a pre-tool-use hook. /// -public class PreToolUseHookOutput +public sealed class PreToolUseHookOutput { /// /// Permission decision for the pending tool call. @@ -1160,15 +1271,10 @@ public class PreToolUseHookOutput public bool? SuppressOutput { get; set; } } -/// -/// Delegate invoked before a tool is executed, allowing modification or denial of the call. -/// -public delegate Task PreToolUseHandler(PreToolUseHookInput input, HookInvocation invocation); - /// /// Input for a post-tool-use hook. /// -public class PostToolUseHookInput +public sealed class PostToolUseHookInput { /// /// The runtime session ID of the session that triggered the hook. @@ -1180,7 +1286,8 @@ public class PostToolUseHookInput /// Unix timestamp in milliseconds when the tool execution completed. /// [JsonPropertyName("timestamp")] - public long Timestamp { get; set; } + [JsonConverter(typeof(UnixMillisecondsDateTimeOffsetConverter))] + public DateTimeOffset Timestamp { get; set; } /// /// Current working directory of the session. @@ -1210,7 +1317,7 @@ public class PostToolUseHookInput /// /// Output for a post-tool-use hook. /// -public class PostToolUseHookOutput +public sealed class PostToolUseHookOutput { /// /// Modified result to replace the original tool result. @@ -1231,15 +1338,10 @@ public class PostToolUseHookOutput public bool? SuppressOutput { get; set; } } -/// -/// Delegate invoked after a tool has been executed, allowing modification of the result. -/// -public delegate Task PostToolUseHandler(PostToolUseHookInput input, HookInvocation invocation); - /// /// Input for a user-prompt-submitted hook. /// -public class UserPromptSubmittedHookInput +public sealed class UserPromptSubmittedHookInput { /// /// The runtime session ID of the session that triggered the hook. @@ -1251,7 +1353,8 @@ public class UserPromptSubmittedHookInput /// Unix timestamp in milliseconds when the prompt was submitted. /// [JsonPropertyName("timestamp")] - public long Timestamp { get; set; } + [JsonConverter(typeof(UnixMillisecondsDateTimeOffsetConverter))] + public DateTimeOffset Timestamp { get; set; } /// /// Current working directory of the session. @@ -1269,7 +1372,7 @@ public class UserPromptSubmittedHookInput /// /// Output for a user-prompt-submitted hook. /// -public class UserPromptSubmittedHookOutput +public sealed class UserPromptSubmittedHookOutput { /// /// Modified prompt to use instead of the original user prompt. @@ -1290,15 +1393,10 @@ public class UserPromptSubmittedHookOutput public bool? SuppressOutput { get; set; } } -/// -/// Delegate invoked when the user submits a prompt, allowing modification of the prompt. -/// -public delegate Task UserPromptSubmittedHandler(UserPromptSubmittedHookInput input, HookInvocation invocation); - /// /// Input for a session-start hook. /// -public class SessionStartHookInput +public sealed class SessionStartHookInput { /// /// The runtime session ID of the session that triggered the hook. @@ -1310,7 +1408,8 @@ public class SessionStartHookInput /// Unix timestamp in milliseconds when the session started. /// [JsonPropertyName("timestamp")] - public long Timestamp { get; set; } + [JsonConverter(typeof(UnixMillisecondsDateTimeOffsetConverter))] + public DateTimeOffset Timestamp { get; set; } /// /// Current working directory of the session. @@ -1339,7 +1438,7 @@ public class SessionStartHookInput /// /// Output for a session-start hook. /// -public class SessionStartHookOutput +public sealed class SessionStartHookOutput { /// /// Additional context to inject into the session for the language model. @@ -1354,15 +1453,10 @@ public class SessionStartHookOutput public IDictionary? ModifiedConfig { get; set; } } -/// -/// Delegate invoked when a session starts, allowing injection of context or config changes. -/// -public delegate Task SessionStartHandler(SessionStartHookInput input, HookInvocation invocation); - /// /// Input for a session-end hook. /// -public class SessionEndHookInput +public sealed class SessionEndHookInput { /// /// The runtime session ID of the session that triggered the hook. @@ -1374,7 +1468,8 @@ public class SessionEndHookInput /// Unix timestamp in milliseconds when the session ended. /// [JsonPropertyName("timestamp")] - public long Timestamp { get; set; } + [JsonConverter(typeof(UnixMillisecondsDateTimeOffsetConverter))] + public DateTimeOffset Timestamp { get; set; } /// /// Current working directory of the session. @@ -1411,7 +1506,7 @@ public class SessionEndHookInput /// /// Output for a session-end hook. /// -public class SessionEndHookOutput +public sealed class SessionEndHookOutput { /// /// Whether to suppress the session end output from the conversation. @@ -1432,15 +1527,10 @@ public class SessionEndHookOutput public string? SessionSummary { get; set; } } -/// -/// Delegate invoked when a session ends, allowing cleanup actions or summary generation. -/// -public delegate Task SessionEndHandler(SessionEndHookInput input, HookInvocation invocation); - /// /// Input for an error-occurred hook. /// -public class ErrorOccurredHookInput +public sealed class ErrorOccurredHookInput { /// /// The runtime session ID of the session that triggered the hook. @@ -1452,7 +1542,8 @@ public class ErrorOccurredHookInput /// Unix timestamp in milliseconds when the error occurred. /// [JsonPropertyName("timestamp")] - public long Timestamp { get; set; } + [JsonConverter(typeof(UnixMillisecondsDateTimeOffsetConverter))] + public DateTimeOffset Timestamp { get; set; } /// /// Current working directory of the session. @@ -1488,7 +1579,7 @@ public class ErrorOccurredHookInput /// /// Output for an error-occurred hook. /// -public class ErrorOccurredHookOutput +public sealed class ErrorOccurredHookOutput { /// /// Whether to suppress the error output from the conversation. @@ -1520,45 +1611,40 @@ public class ErrorOccurredHookOutput public string? UserNotification { get; set; } } -/// -/// Delegate invoked when an error occurs, allowing custom error handling strategies. -/// -public delegate Task ErrorOccurredHandler(ErrorOccurredHookInput input, HookInvocation invocation); - /// /// Hook handlers configuration for a session. /// -public class SessionHooks +public sealed class SessionHooks { /// /// Handler called before a tool is executed. /// - public PreToolUseHandler? OnPreToolUse { get; set; } + public Func>? OnPreToolUse { get; set; } /// /// Handler called after a tool has been executed. /// - public PostToolUseHandler? OnPostToolUse { get; set; } + public Func>? OnPostToolUse { get; set; } /// /// Handler called when the user submits a prompt. /// - public UserPromptSubmittedHandler? OnUserPromptSubmitted { get; set; } + public Func>? OnUserPromptSubmitted { get; set; } /// /// Handler called when a session starts. /// - public SessionStartHandler? OnSessionStart { get; set; } + public Func>? OnSessionStart { get; set; } /// /// Handler called when a session ends. /// - public SessionEndHandler? OnSessionEnd { get; set; } + public Func>? OnSessionEnd { get; set; } /// /// Handler called when an error occurs. /// - public ErrorOccurredHandler? OnErrorOccurred { get; set; } + public Func>? OnErrorOccurred { get; set; } } /// @@ -1604,7 +1690,7 @@ public enum SectionOverrideAction /// /// Override operation for a single system prompt section. /// -public class SectionOverride +public sealed class SectionOverride { /// /// The operation to perform on this section. Ignored when Transform is set. @@ -1657,7 +1743,7 @@ public static class SystemPromptSections /// /// Configuration for the system message used in a session. /// -public class SystemMessageConfig +public sealed class SystemMessageConfig { /// /// How the system message is applied (append, replace, or customize). @@ -1680,7 +1766,7 @@ public class SystemMessageConfig /// /// Configuration for a custom model provider. /// -public class ProviderConfig +public sealed class ProviderConfig { /// /// Provider type identifier (e.g., "openai", "azure"). @@ -1730,7 +1816,7 @@ public class ProviderConfig /// Well-known model name used by the runtime to look up agent configuration /// (tools, prompts, reasoning behavior) and default token limits. Also used /// as the wire model when is not set. - /// Falls back to . + /// Falls back to . /// [JsonPropertyName("modelId")] public string? ModelId { get; set; } @@ -1739,7 +1825,7 @@ public class ProviderConfig /// Model name sent to the provider API for inference. Use this when the /// provider's model name (e.g. an Azure deployment name or a custom /// fine-tune name) differs from . - /// Falls back to , then . + /// Falls back to , then . /// [JsonPropertyName("wireModel")] public string? WireModel { get; set; } @@ -1751,7 +1837,7 @@ public class ProviderConfig /// exceed this limit. /// [JsonPropertyName("maxPromptTokens")] - public int? MaxInputTokens { get; set; } + public int? MaxPromptTokens { get; set; } /// /// Overrides the resolved model's default max output tokens. When hit, the @@ -1764,7 +1850,7 @@ public class ProviderConfig /// /// Azure OpenAI-specific provider options. /// -public class AzureOptions +public sealed class AzureOptions { /// /// Azure OpenAI API version to use (e.g., "2024-02-01"). @@ -1805,10 +1891,12 @@ public abstract class McpServerConfig private protected McpServerConfig() { } /// - /// List of tools to include from this server. Empty list means none. Use "*" for all. + /// List of tools to include from this server. null (the default) + /// means include all tools. An empty list means include none. /// [JsonPropertyName("tools")] - public IList Tools { get => field ??= []; set; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public IList? Tools { get; set; } /// /// The server type discriminator. @@ -1854,7 +1942,7 @@ public sealed class McpStdioServerConfig : McpServerConfig /// Working directory for the server process. /// [JsonPropertyName("cwd")] - public string? Cwd { get; set; } + public string? WorkingDirectory { get; set; } } /// @@ -1904,7 +1992,7 @@ public sealed class McpHttpServerConfig : McpServerConfig /// /// Configuration for a custom agent. /// -public class CustomAgentConfig +public sealed class CustomAgentConfig { /// /// Unique name of the custom agent. @@ -1952,7 +2040,7 @@ public class CustomAgentConfig /// List of skill names to preload into this agent's context. /// When set, the full content of each listed skill is eagerly injected into /// the agent's context at startup. Skills are resolved by name from the - /// session's configured skill directories (). + /// session's configured skill directories (). /// When omitted, no skills are injected (opt-in model). /// [JsonPropertyName("skills")] @@ -1972,7 +2060,7 @@ public class CustomAgentConfig /// Use to hide specific tools from the default agent /// while keeping them available to custom sub-agents. /// -public class DefaultAgentConfig +public sealed class DefaultAgentConfig { /// /// List of tool names to exclude from the default agent. @@ -1987,7 +2075,7 @@ public class DefaultAgentConfig /// When enabled, sessions automatically manage context window limits through background compaction /// and persist state to a workspace directory. /// -public class InfiniteSessionConfig +public sealed class InfiniteSessionConfig { /// /// Whether infinite sessions are enabled. Default: true @@ -2015,7 +2103,7 @@ public class InfiniteSessionConfig /// /// GitHub repository metadata to associate with a cloud session. /// -public class CloudSessionRepository +public sealed class CloudSessionRepository { /// Repository owner. public required string Owner { get; set; } @@ -2030,7 +2118,7 @@ public class CloudSessionRepository /// /// Options for creating a remote session in the cloud. /// -public class CloudSessionOptions +public sealed class CloudSessionOptions { /// /// Optional GitHub repository metadata to associate with the cloud session. @@ -2039,20 +2127,20 @@ public class CloudSessionOptions } /// -/// Configuration options for creating a new Copilot session. +/// Shared configuration properties for creating or resuming a Copilot session. +/// Use when creating a new session, or +/// when resuming an existing one. /// -public class SessionConfig +public abstract class SessionConfigBase { - /// - /// Initializes a new instance of the class. - /// - public SessionConfig() { } + /// Initializes a new instance of the class. + protected SessionConfigBase() { } /// - /// Initializes a new instance of the class - /// by copying the properties of the specified instance. + /// Initializes a new instance of by copying the + /// properties of the specified instance. /// - protected SessionConfig(SessionConfig? other) + protected SessionConfigBase(SessionConfigBase? other) { if (other is null) return; @@ -2075,20 +2163,18 @@ protected SessionConfig(SessionConfig? other) : null; Model = other.Model; ModelCapabilities = other.ModelCapabilities; - OnAutoModeSwitch = other.OnAutoModeSwitch; + OnAutoModeSwitchRequest = other.OnAutoModeSwitchRequest; OnElicitationRequest = other.OnElicitationRequest; OnEvent = other.OnEvent; - OnExitPlanMode = other.OnExitPlanMode; + OnExitPlanModeRequest = other.OnExitPlanModeRequest; OnPermissionRequest = other.OnPermissionRequest; OnUserInputRequest = other.OnUserInputRequest; Provider = other.Provider; EnableSessionTelemetry = other.EnableSessionTelemetry; ReasoningEffort = other.ReasoningEffort; - CreateSessionFsHandler = other.CreateSessionFsHandler; + CreateSessionFsProvider = other.CreateSessionFsProvider; GitHubToken = other.GitHubToken; RemoteSession = other.RemoteSession; - Cloud = other.Cloud; - SessionId = other.SessionId; SkillDirectories = other.SkillDirectories is not null ? [.. other.SkillDirectories] : null; InstructionDirectories = other.InstructionDirectories is not null ? [.. other.InstructionDirectories] : null; Streaming = other.Streaming; @@ -2098,20 +2184,10 @@ protected SessionConfig(SessionConfig? other) WorkingDirectory = other.WorkingDirectory; } - /// - /// Optional session identifier; a new ID is generated if not provided. - /// - public string? SessionId { get; set; } - - /// - /// Client name to identify the application using the SDK. - /// Included in the User-Agent header for API requests. - /// + /// Client name to identify the application using the SDK. public string? ClientName { get; set; } - /// - /// Model identifier to use for this session (e.g., "gpt-4o"). - /// + /// Model identifier to use for this session (e.g., "gpt-4o"). public string? Model { get; set; } /// @@ -2121,9 +2197,7 @@ protected SessionConfig(SessionConfig? other) /// public string? ReasoningEffort { get; set; } - /// - /// Per-property overrides for model capabilities, deep-merged over runtime defaults. - /// + /// Per-property overrides for model capabilities, deep-merged over runtime defaults. public ModelCapabilitiesOverride? ModelCapabilities { get; set; } /// @@ -2151,21 +2225,17 @@ protected SessionConfig(SessionConfig? other) /// are left for the client to handle via external tool request events. /// public ICollection? Tools { get; set; } - /// - /// System message configuration for the session. - /// + + /// System message configuration for the session. public SystemMessageConfig? SystemMessage { get; set; } - /// - /// List of tool names to allow; only these tools will be available when specified. - /// + + /// List of tool names to allow; only these tools will be available when specified. public IList? AvailableTools { get; set; } - /// - /// List of tool names to exclude from the session. - /// + + /// List of tool names to exclude from the session. public IList? ExcludedTools { get; set; } - /// - /// Custom model provider configuration for the session. - /// + + /// Custom model provider configuration for the session. public ProviderConfig? Provider { get; set; } /// @@ -2179,52 +2249,28 @@ protected SessionConfig(SessionConfig? other) /// public bool? EnableSessionTelemetry { get; set; } - /// - /// Handler for permission requests from the server. - /// When provided, the server will call this handler to request permission for operations. - /// - public PermissionRequestHandler? OnPermissionRequest { get; set; } + /// Handler for permission requests from the server. + public Func>? OnPermissionRequest { get; set; } - /// - /// Handler for user input requests from the agent. - /// When provided, enables the ask_user tool for the agent to request user input. - /// - public UserInputHandler? OnUserInputRequest { get; set; } + /// Handler for user input requests from the agent. + public Func>? OnUserInputRequest { get; set; } - /// - /// Slash commands registered for this session. - /// When the CLI has a TUI, each command appears as /name for the user to invoke. - /// The handler is called when the user executes the command. - /// + /// Slash commands registered for this session. public IList? Commands { get; set; } - /// - /// Handler for elicitation requests from the server or MCP tools. - /// When provided, the server will route elicitation requests to this handler - /// and report elicitation as a supported capability. - /// - public ElicitationHandler? OnElicitationRequest { get; set; } + /// Handler for elicitation requests from the server or MCP tools. + public Func>? OnElicitationRequest { get; set; } - /// - /// Handler for exit-plan-mode requests from the server. - /// When provided, the server will route exitPlanMode.request callbacks to this handler. - /// - public ExitPlanModeHandler? OnExitPlanMode { get; set; } + /// Handler for exit-plan-mode requests from the server. + public Func>? OnExitPlanModeRequest { get; set; } - /// - /// Handler for auto-mode-switch requests from the server. - /// When provided, the server will route autoModeSwitch.request callbacks to this handler. - /// - public AutoModeSwitchHandler? OnAutoModeSwitch { get; set; } + /// Handler for auto-mode-switch requests from the server. + public Func>? OnAutoModeSwitchRequest { get; set; } - /// - /// Hook handlers for session lifecycle events. - /// + /// Hook handlers for session lifecycle events. public SessionHooks? Hooks { get; set; } - /// - /// Working directory for the session. - /// + /// Working directory for the session. public string? WorkingDirectory { get; set; } /// @@ -2232,7 +2278,8 @@ protected SessionConfig(SessionConfig? other) /// When true, assistant.message_delta and assistant.reasoning_delta events /// with deltaContent are sent as the response is generated. /// - public bool Streaming { get; set; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public bool? Streaming { get; set; } /// /// Include sub-agent streaming events in the event stream. When true, streaming @@ -2251,9 +2298,7 @@ protected SessionConfig(SessionConfig? other) /// public IDictionary? McpServers { get; set; } - /// - /// Custom agent configurations for the session. - /// + /// Custom agent configurations for the session. public IList? CustomAgents { get; set; } /// @@ -2269,19 +2314,13 @@ protected SessionConfig(SessionConfig? other) /// public string? Agent { get; set; } - /// - /// Directories to load skills from. - /// + /// Directories to load skills from. public IList? SkillDirectories { get; set; } - /// - /// Additional directories to search for custom instruction files. - /// + /// Additional directories to search for custom instruction files. public IList? InstructionDirectories { get; set; } - /// - /// List of skill names to disable. - /// + /// List of skill names to disable. public IList? DisabledSkills { get; set; } /// @@ -2291,22 +2330,16 @@ protected SessionConfig(SessionConfig? other) public InfiniteSessionConfig? InfiniteSessions { get; set; } /// - /// Optional event handler that is registered on the session before the - /// session.create RPC is issued. + /// Optional event handler registered on the session before the session.create / session.resume + /// RPC is issued, ensuring early events are delivered. /// - /// - /// Equivalent to calling immediately - /// after creation, but executes earlier in the lifecycle so no events are missed. - /// Using this property rather than guarantees that early events emitted - /// by the CLI during session creation (e.g. session.start) are delivered to the handler. - /// - public SessionEventHandler? OnEvent { get; set; } + public Action? OnEvent { get; set; } /// /// Supplies a handler for session filesystem operations. /// This is used only when is configured. /// - public Func? CreateSessionFsHandler { get; set; } + public Func? CreateSessionFsProvider { get; set; } /// /// GitHub token for per-session authentication. @@ -2324,6 +2357,30 @@ protected SessionConfig(SessionConfig? other) /// /// public RemoteSessionMode? RemoteSession { get; set; } +} + +/// +/// Configuration options for creating a new Copilot session. +/// +public sealed class SessionConfig : SessionConfigBase +{ + /// Initializes a new instance of the class. + public SessionConfig() { } + + /// + /// Initializes a new instance of by copying the + /// properties of the specified instance. + /// + private SessionConfig(SessionConfig? other) : base(other) + { + if (other is null) return; + + SessionId = other.SessionId; + Cloud = other.Cloud; + } + + /// Optional session identifier; a new ID is generated if not provided. + public string? SessionId { get; set; } /// /// Creates a remote session in the cloud instead of a local session. @@ -2341,205 +2398,34 @@ protected SessionConfig(SessionConfig? other) /// hooks, infinite session configuration, and delegates) are not deep-cloned; the original /// and the clone will share those nested objects, and changes to them may affect both. /// - public virtual SessionConfig Clone() - { - return new(this); - } + public SessionConfig Clone() => new(this); } /// /// Configuration options for resuming an existing Copilot session. /// -public class ResumeSessionConfig +public sealed class ResumeSessionConfig : SessionConfigBase { - /// - /// Initializes a new instance of the class. - /// + /// Initializes a new instance of the class. public ResumeSessionConfig() { } /// - /// Initializes a new instance of the class - /// by copying the properties of the specified instance. + /// Initializes a new instance of by copying the + /// properties of the specified instance. /// - protected ResumeSessionConfig(ResumeSessionConfig? other) + private ResumeSessionConfig(ResumeSessionConfig? other) : base(other) { if (other is null) return; - AvailableTools = other.AvailableTools is not null ? [.. other.AvailableTools] : null; - ClientName = other.ClientName; - Commands = other.Commands is not null ? [.. other.Commands] : null; - ConfigDir = other.ConfigDir; - CustomAgents = other.CustomAgents is not null ? [.. other.CustomAgents] : null; - DefaultAgent = other.DefaultAgent; - Agent = other.Agent; - DisabledSkills = other.DisabledSkills is not null ? [.. other.DisabledSkills] : null; - DisableResume = other.DisableResume; - EnableConfigDiscovery = other.EnableConfigDiscovery; + SuppressResumeEvent = other.SuppressResumeEvent; ContinuePendingWork = other.ContinuePendingWork; - ExcludedTools = other.ExcludedTools is not null ? [.. other.ExcludedTools] : null; - Hooks = other.Hooks; - InfiniteSessions = other.InfiniteSessions; - McpServers = other.McpServers is not null - ? (other.McpServers is Dictionary dict - ? new Dictionary(dict, dict.Comparer) - : new Dictionary(other.McpServers)) - : null; - Model = other.Model; - ModelCapabilities = other.ModelCapabilities; - OnAutoModeSwitch = other.OnAutoModeSwitch; - OnElicitationRequest = other.OnElicitationRequest; - OnEvent = other.OnEvent; - OnExitPlanMode = other.OnExitPlanMode; - OnPermissionRequest = other.OnPermissionRequest; - OnUserInputRequest = other.OnUserInputRequest; - Provider = other.Provider; - EnableSessionTelemetry = other.EnableSessionTelemetry; - ReasoningEffort = other.ReasoningEffort; - CreateSessionFsHandler = other.CreateSessionFsHandler; - GitHubToken = other.GitHubToken; - RemoteSession = other.RemoteSession; - SkillDirectories = other.SkillDirectories is not null ? [.. other.SkillDirectories] : null; - InstructionDirectories = other.InstructionDirectories is not null ? [.. other.InstructionDirectories] : null; - Streaming = other.Streaming; - IncludeSubAgentStreamingEvents = other.IncludeSubAgentStreamingEvents; - SystemMessage = other.SystemMessage; - Tools = other.Tools is not null ? [.. other.Tools] : null; - WorkingDirectory = other.WorkingDirectory; } - /// - /// Client name to identify the application using the SDK. - /// Included in the User-Agent header for API requests. - /// - public string? ClientName { get; set; } - - /// - /// Model to use for this session. Can change the model when resuming. - /// - public string? Model { get; set; } - - /// - /// Custom tool declarations available to the language model during the resumed session. - /// Declarations backed by an are invoked automatically; declarations without one - /// are left for the client to handle via external tool request events. - /// - public ICollection? Tools { get; set; } - - /// - /// System message configuration. - /// - public SystemMessageConfig? SystemMessage { get; set; } - - /// - /// List of tool names to allow. When specified, only these tools will be available. - /// Takes precedence over ExcludedTools. - /// - public IList? AvailableTools { get; set; } - - /// - /// List of tool names to disable. All other tools remain available. - /// Ignored if AvailableTools is specified. - /// - public IList? ExcludedTools { get; set; } - - /// - /// Custom model provider configuration for the resumed session. - /// - public ProviderConfig? Provider { get; set; } - - /// - /// Enables or disables internal session telemetry for this session. - /// When false, disables session telemetry. When null (the default) or true, - /// telemetry is enabled for GitHub-authenticated sessions. - /// When a custom (BYOK) is configured, session telemetry is - /// always disabled regardless of this setting. - /// This is independent of , which configures - /// OpenTelemetry export for observability. - /// - public bool? EnableSessionTelemetry { get; set; } - - /// - /// Reasoning effort level for models that support it. - /// Valid values: "low", "medium", "high", "xhigh". - /// - public string? ReasoningEffort { get; set; } - - /// - /// Per-property overrides for model capabilities, deep-merged over runtime defaults. - /// - public ModelCapabilitiesOverride? ModelCapabilities { get; set; } - - /// - /// Handler for permission requests from the server. - /// When provided, the server will call this handler to request permission for operations. - /// - public PermissionRequestHandler? OnPermissionRequest { get; set; } - - /// - /// Handler for user input requests from the agent. - /// When provided, enables the ask_user tool for the agent to request user input. - /// - public UserInputHandler? OnUserInputRequest { get; set; } - - /// - /// Slash commands registered for this session. - /// When the CLI has a TUI, each command appears as /name for the user to invoke. - /// The handler is called when the user executes the command. - /// - public IList? Commands { get; set; } - - /// - /// Handler for elicitation requests from the server or MCP tools. - /// When provided, the server will route elicitation requests to this handler - /// and report elicitation as a supported capability. - /// - public ElicitationHandler? OnElicitationRequest { get; set; } - - /// - /// Handler for exit-plan-mode requests from the server. - /// When provided, the server will route exitPlanMode.request callbacks to this handler. - /// - public ExitPlanModeHandler? OnExitPlanMode { get; set; } - - /// - /// Handler for auto-mode-switch requests from the server. - /// When provided, the server will route autoModeSwitch.request callbacks to this handler. - /// - public AutoModeSwitchHandler? OnAutoModeSwitch { get; set; } - - /// - /// Hook handlers for session lifecycle events. - /// - public SessionHooks? Hooks { get; set; } - - /// - /// Working directory for the session. - /// - public string? WorkingDirectory { get; set; } - - /// - /// Override the default configuration directory location. - /// - public string? ConfigDir { get; set; } - - /// - /// When , automatically discovers MCP server configurations - /// (e.g. .mcp.json, .vscode/mcp.json) and skill directories from - /// the working directory and merges them with any explicitly provided - /// and , with explicit - /// values taking precedence on name collision. - /// - /// Custom instruction files (.github/copilot-instructions.md, AGENTS.md, etc.) - /// are always loaded from the working directory regardless of this setting. - /// - /// - public bool? EnableConfigDiscovery { get; set; } - /// /// When true, the session.resume event is not emitted. /// Default: false (resume event is emitted). /// - public bool DisableResume { get; set; } + public bool SuppressResumeEvent { get; set; } /// /// When , instructs the runtime to continue any tool calls @@ -2548,100 +2434,13 @@ protected ResumeSessionConfig(ResumeSessionConfig? other) /// interrupted on resume. /// /// For permission requests, the runtime re-emits permission.requested so the - /// registered handler can re-prompt; for external - /// tool calls, the consumer is expected to supply the result via the corresponding - /// low-level RPC method. + /// registered handler can re-prompt; + /// for external tool calls, the consumer is expected to supply the result via the + /// corresponding low-level RPC method. /// /// public bool? ContinuePendingWork { get; set; } - /// - /// Enable streaming of assistant message and reasoning chunks. - /// When true, assistant.message_delta and assistant.reasoning_delta events - /// with deltaContent are sent as the response is generated. - /// - public bool Streaming { get; set; } - - /// - /// Include sub-agent streaming events in the event stream. When true, streaming - /// delta events from sub-agents (e.g., assistant.message_delta, - /// assistant.reasoning_delta, assistant.streaming_delta with - /// agentId set) are forwarded to this connection. When false, only - /// non-streaming sub-agent events and subagent.* lifecycle events are - /// forwarded; streaming deltas from sub-agents are suppressed. - /// Default: true. - /// - public bool IncludeSubAgentStreamingEvents { get; set; } = true; - - /// - /// MCP server configurations for the session. - /// Keys are server names, values are server configurations ( or ). - /// - public IDictionary? McpServers { get; set; } - - /// - /// Custom agent configurations for the session. - /// - public IList? CustomAgents { get; set; } - - /// - /// Configuration for the default agent (the built-in agent that handles turns when no custom agent is selected). - /// Use to hide specific tools from the default agent - /// while keeping them available to custom sub-agents. - /// - public DefaultAgentConfig? DefaultAgent { get; set; } - - /// - /// Name of the custom agent to activate when the session starts. - /// Must match the of one of the agents in . - /// - public string? Agent { get; set; } - - /// - /// Directories to load skills from. - /// - public IList? SkillDirectories { get; set; } - - /// - /// Additional directories to search for custom instruction files. - /// - public IList? InstructionDirectories { get; set; } - - /// - /// List of skill names to disable. - /// - public IList? DisabledSkills { get; set; } - - /// - /// Infinite session configuration for persistent workspaces and automatic compaction. - /// - public InfiniteSessionConfig? InfiniteSessions { get; set; } - - /// - /// Optional event handler registered before the session.resume RPC is issued, - /// ensuring early events are delivered. See . - /// - public SessionEventHandler? OnEvent { get; set; } - - /// - /// Supplies a handler for session filesystem operations. - /// This is used only when is configured. - /// - public Func? CreateSessionFsHandler { get; set; } - - /// - /// GitHub token for per-session authentication. - /// When provided, the runtime resolves this token into a full GitHub identity - /// and stores it on the session for content exclusion, model routing, and quota checks. - /// - public string? GitHubToken { get; set; } - - /// - /// Per-session remote behavior control. - /// See for details. - /// - public RemoteSessionMode? RemoteSession { get; set; } - /// /// Creates a shallow clone of this instance. /// @@ -2652,16 +2451,13 @@ protected ResumeSessionConfig(ResumeSessionConfig? other) /// hooks, infinite session configuration, and delegates) are not deep-cloned; the original /// and the clone will share those nested objects, and changes to them may affect both. /// - public virtual ResumeSessionConfig Clone() - { - return new(this); - } + public ResumeSessionConfig Clone() => new(this); } /// /// Options for sending a message in a Copilot session. /// -public class MessageOptions +public sealed class MessageOptions { /// /// Initializes a new instance of the class. @@ -2672,7 +2468,7 @@ public MessageOptions() { } /// Initializes a new instance of the class /// by copying the properties of the specified instance. /// - protected MessageOptions(MessageOptions? other) + private MessageOptions(MessageOptions? other) { if (other is null) return; @@ -2710,21 +2506,16 @@ protected MessageOptions(MessageOptions? other) /// Other reference-type properties (for example attachment items) are not deep-cloned; /// the original and the clone will share those nested objects. /// - public virtual MessageOptions Clone() + public MessageOptions Clone() { return new(this); } } -/// -/// Delegate for handling session events emitted during a Copilot session. -/// -public delegate void SessionEventHandler(SessionEvent sessionEvent); - /// /// Working directory context for a session. /// -public class SessionContext +public sealed class SessionContext { /// Working directory where the session was created. public string Cwd { get; set; } = string.Empty; @@ -2739,10 +2530,10 @@ public class SessionContext /// /// Filter options for listing sessions. /// -public class SessionListFilter +public sealed class SessionListFilter { - /// Filter by exact cwd match. - public string? Cwd { get; set; } + /// Filter by exact working directory match. + public string? WorkingDirectory { get; set; } /// Filter by git root. public string? GitRoot { get; set; } /// Filter by repository (owner/repo format). @@ -2754,7 +2545,7 @@ public class SessionListFilter /// /// Metadata describing a Copilot session. /// -public class SessionMetadata +public sealed class SessionMetadata { /// /// Unique identifier of the session. @@ -2763,11 +2554,11 @@ public class SessionMetadata /// /// Time when the session was created. /// - public DateTime StartTime { get; set; } + public DateTimeOffset StartTime { get; set; } /// /// Time when the session was last modified. /// - public DateTime ModifiedTime { get; set; } + public DateTimeOffset ModifiedTime { get; set; } /// /// Human-readable summary of the session. /// @@ -2788,7 +2579,7 @@ internal class PingRequest /// /// Response from a server ping request. /// -public class PingResponse +public sealed class PingResponse { /// /// Echo of the ping message. @@ -2807,7 +2598,7 @@ public class PingResponse /// /// Response from status.get /// -public class GetStatusResponse +public sealed class GetStatusResponse { /// Package version (e.g., "1.0.0") [JsonPropertyName("version")] @@ -2821,7 +2612,7 @@ public class GetStatusResponse /// /// Response from auth.getStatus /// -public class GetAuthStatusResponse +public sealed class GetAuthStatusResponse { /// Whether the user is authenticated [JsonPropertyName("isAuthenticated")] @@ -2857,7 +2648,7 @@ public class GetAuthStatusResponse /// /// Model vision-specific limits /// -public class ModelVisionLimits +public sealed class ModelVisionLimits { /// /// List of supported image MIME types (e.g., "image/png", "image/jpeg"). @@ -2881,7 +2672,7 @@ public class ModelVisionLimits /// /// Model limits /// -public class ModelLimits +public sealed class ModelLimits { /// /// Maximum number of tokens allowed in the prompt. @@ -2905,7 +2696,7 @@ public class ModelLimits /// /// Model support flags /// -public class ModelSupports +public sealed class ModelSupports { /// /// Whether this model supports image/vision inputs. @@ -2923,7 +2714,7 @@ public class ModelSupports /// /// Model capabilities and limits /// -public class ModelCapabilities +public sealed class ModelCapabilities { /// /// Feature support flags for the model. @@ -2941,7 +2732,7 @@ public class ModelCapabilities /// /// Model policy state /// -public class ModelPolicy +public sealed class ModelPolicy { /// /// Policy state of the model (e.g., "enabled", "disabled"). @@ -2959,7 +2750,7 @@ public class ModelPolicy /// /// Model billing information /// -public class ModelBilling +public sealed class ModelBilling { /// /// Billing cost multiplier relative to the base model rate. @@ -2971,7 +2762,7 @@ public class ModelBilling /// /// Information about an available model /// -public class ModelInfo +public sealed class ModelInfo { /// Model identifier (e.g., "claude-sonnet-4.5") [JsonPropertyName("id")] @@ -3005,7 +2796,7 @@ public class ModelInfo /// /// Response from models.list /// -public class GetModelsResponse +public sealed class GetModelsResponse { /// /// List of available models. @@ -3019,38 +2810,21 @@ public class GetModelsResponse // ============================================================================ /// -/// Types of session lifecycle events -/// -public static class SessionLifecycleEventTypes -{ - /// A new session was created. - public const string Created = "session.created"; - /// A session was deleted. - public const string Deleted = "session.deleted"; - /// A session was updated. - public const string Updated = "session.updated"; - /// A session was brought to the foreground. - public const string Foreground = "session.foreground"; - /// A session was moved to the background. - public const string Background = "session.background"; -} - -/// -/// Metadata for session lifecycle events +/// Metadata for session lifecycle events. /// -public class SessionLifecycleEventMetadata +public sealed class SessionLifecycleEventMetadata { /// - /// ISO 8601 timestamp when the session was created. + /// Timestamp when the session was created. /// [JsonPropertyName("startTime")] - public string StartTime { get; set; } = string.Empty; + public DateTimeOffset StartTime { get; set; } /// - /// ISO 8601 timestamp when the session was last modified. + /// Timestamp when the session was last modified. /// [JsonPropertyName("modifiedTime")] - public string ModifiedTime { get; set; } = string.Empty; + public DateTimeOffset ModifiedTime { get; set; } /// /// Human-readable summary of the session. @@ -3060,12 +2834,20 @@ public class SessionLifecycleEventMetadata } /// -/// Session lifecycle event notification +/// Session lifecycle event notification. Use derived types +/// (, , +/// , , +/// ) for known kinds. The base type is +/// instantiated when the runtime emits an event kind not known to this SDK +/// version, so consumers can still inspect for forward +/// compatibility. /// public class SessionLifecycleEvent { /// - /// Type of lifecycle event (see ). + /// Wire-format type discriminator (e.g., "session.created"). Useful + /// when the runtime emits an event kind not yet known to this SDK; for + /// known kinds, prefer pattern-matching on the derived type instead. /// [JsonPropertyName("type")] public string Type { get; set; } = string.Empty; @@ -3083,10 +2865,25 @@ public class SessionLifecycleEvent public SessionLifecycleEventMetadata? Metadata { get; set; } } +/// Raised when a new session is created. +public sealed class SessionCreatedEvent : SessionLifecycleEvent { } + +/// Raised when a session is deleted. +public sealed class SessionDeletedEvent : SessionLifecycleEvent { } + +/// Raised when a session's metadata is updated. +public sealed class SessionUpdatedEvent : SessionLifecycleEvent { } + +/// Raised when a session is brought to the foreground (TUI+server mode). +public sealed class SessionForegroundEvent : SessionLifecycleEvent { } + +/// Raised when a session moves to the background (TUI+server mode). +public sealed class SessionBackgroundEvent : SessionLifecycleEvent { } + /// /// Response from session.getForeground /// -public class GetForegroundSessionResponse +public sealed class GetForegroundSessionResponse { /// /// Identifier of the current foreground session, or null if none. @@ -3104,7 +2901,7 @@ public class GetForegroundSessionResponse /// /// Response from session.setForeground /// -public class SetForegroundSessionResponse +public sealed class SetForegroundSessionResponse { /// /// Whether the foreground session was set successfully. @@ -3122,7 +2919,7 @@ public class SetForegroundSessionResponse /// /// Content data for a single system prompt section in a transform RPC call. /// -public class SystemMessageTransformSection +public sealed class SystemMessageTransformSection { /// /// The content of the section. @@ -3134,7 +2931,7 @@ public class SystemMessageTransformSection /// /// Response to a systemMessage.transform RPC call. /// -public class SystemMessageTransformRpcResponse +public sealed class SystemMessageTransformRpcResponse { /// /// The transformed sections keyed by section identifier. @@ -3182,8 +2979,12 @@ public class SystemMessageTransformRpcResponse [JsonSerializable(typeof(SetForegroundSessionResponse))] [JsonSerializable(typeof(SystemMessageConfig))] [JsonSerializable(typeof(ToolBinaryResult))] +[JsonSerializable(typeof(ToolBinaryResultType))] [JsonSerializable(typeof(ToolInvocation))] [JsonSerializable(typeof(ToolResultObject))] [JsonSerializable(typeof(JsonElement))] [JsonSerializable(typeof(JsonElement?))] +[JsonSerializable(typeof(object))] +[JsonSerializable(typeof(Dictionary))] +[JsonSerializable(typeof(string[]))] internal partial class TypesJsonContext : JsonSerializerContext; diff --git a/dotnet/src/UnixMillisecondsDateTimeOffsetConverter.cs b/dotnet/src/UnixMillisecondsDateTimeOffsetConverter.cs new file mode 100644 index 000000000..4b8fcc361 --- /dev/null +++ b/dotnet/src/UnixMillisecondsDateTimeOffsetConverter.cs @@ -0,0 +1,22 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +using System.ComponentModel; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace GitHub.Copilot; + +/// Converts between JSON numeric milliseconds-since-Unix-epoch and . +[EditorBrowsable(EditorBrowsableState.Never)] +public sealed class UnixMillisecondsDateTimeOffsetConverter : JsonConverter +{ + /// + public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => + DateTimeOffset.FromUnixTimeMilliseconds(reader.GetInt64()); + + /// + public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options) => + writer.WriteNumberValue(value.ToUnixTimeMilliseconds()); +} diff --git a/dotnet/test/ConnectionTokenTests.cs b/dotnet/test/ConnectionTokenTests.cs index dc6f115ba..524ff2586 100644 --- a/dotnet/test/ConnectionTokenTests.cs +++ b/dotnet/test/ConnectionTokenTests.cs @@ -2,10 +2,10 @@ * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ -using GitHub.Copilot.SDK.Test.Harness; +using GitHub.Copilot.Test.Harness; using Xunit; -namespace GitHub.Copilot.SDK.Test; +namespace GitHub.Copilot.Test; /// /// Custom fixture that spawns a CLI in TCP mode with an explicit connection token, so @@ -22,14 +22,11 @@ public class ConnectionTokenTestFixture : IAsyncLifetime public async Task InitializeAsync() { Ctx = await E2ETestContext.CreateAsync(); - GoodClient = Ctx.CreateClient(useStdio: false, options: new CopilotClientOptions - { - TcpConnectionToken = Token, - }); + GoodClient = Ctx.CreateClient(options: new CopilotClientOptions { Connection = RuntimeConnection.ForTcp(connectionToken: Token) }); await GoodClient.StartAsync(); - Port = GoodClient.ActualPort - ?? throw new InvalidOperationException("GoodClient is not using TCP mode; ActualPort is null"); + Port = GoodClient.RuntimePort + ?? throw new InvalidOperationException("GoodClient is not using TCP mode; RuntimePort is null"); } public async Task DisposeAsync() @@ -62,11 +59,7 @@ public async Task Connects_With_The_Matching_Token() [Fact] public async Task Rejects_A_Wrong_Token() { - var wrongClient = new CopilotClient(new CopilotClientOptions - { - CliUrl = $"localhost:{_fixture.Port}", - TcpConnectionToken = "wrong", - }); + var wrongClient = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri($"localhost:{_fixture.Port}", connectionToken: "wrong") }); try { @@ -83,10 +76,7 @@ public async Task Rejects_A_Wrong_Token() [Fact] public async Task Rejects_A_Missing_Token_When_One_Is_Required() { - var noTokenClient = new CopilotClient(new CopilotClientOptions - { - CliUrl = $"localhost:{_fixture.Port}", - }); + var noTokenClient = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri($"localhost:{_fixture.Port}") }); try { diff --git a/dotnet/test/E2E/AbortE2ETests.cs b/dotnet/test/E2E/AbortE2ETests.cs index 009ca1e29..ea24610b7 100644 --- a/dotnet/test/E2E/AbortE2ETests.cs +++ b/dotnet/test/E2E/AbortE2ETests.cs @@ -7,7 +7,7 @@ using Xunit; using Xunit.Abstractions; -namespace GitHub.Copilot.SDK.Test.E2E; +namespace GitHub.Copilot.Test.E2E; /// /// Verifies that cleanly interrupts an active @@ -25,7 +25,7 @@ public async Task Should_Abort_During_Active_Streaming() var firstDeltaReceived = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var allEvents = new List(); - session.On(evt => + session.On(evt => { lock (allEvents) { allEvents.Add(evt); } if (evt is AssistantMessageDeltaEvent delta) @@ -60,7 +60,7 @@ public async Task Should_Abort_During_Active_Streaming() // recovery message rather than racing against a late idle from the // aborted streaming turn. var recoveryReceived = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - session.On(evt => + session.On(evt => { if (evt is AssistantMessageEvent msg && (msg.Data.Content?.Contains("abort_recovery_ok") == true)) { @@ -109,7 +109,7 @@ public async Task Should_Abort_During_Active_Tool_Execution() // Session should be usable after abort — verify by listening for the right event var recoveryReceived = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - session.On(evt => + session.On(evt => { if (evt is AssistantMessageEvent msg && (msg.Data.Content?.Contains("tool_abort_recovery_ok") == true)) { diff --git a/dotnet/test/E2E/AskUserE2ETests.cs b/dotnet/test/E2E/AskUserE2ETests.cs index cd79652d0..62faff367 100644 --- a/dotnet/test/E2E/AskUserE2ETests.cs +++ b/dotnet/test/E2E/AskUserE2ETests.cs @@ -2,11 +2,11 @@ * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ -using GitHub.Copilot.SDK.Test.Harness; +using GitHub.Copilot.Test.Harness; using Xunit; using Xunit.Abstractions; -namespace GitHub.Copilot.SDK.Test.E2E; +namespace GitHub.Copilot.Test.E2E; public class AskUserE2ETests(E2ETestFixture fixture, ITestOutputHelper output) : E2ETestBase(fixture, "ask_user", output) { diff --git a/dotnet/test/E2E/BuiltinToolsE2ETests.cs b/dotnet/test/E2E/BuiltinToolsE2ETests.cs index 76bbcf190..863331e9d 100644 --- a/dotnet/test/E2E/BuiltinToolsE2ETests.cs +++ b/dotnet/test/E2E/BuiltinToolsE2ETests.cs @@ -5,7 +5,7 @@ using Xunit; using Xunit.Abstractions; -namespace GitHub.Copilot.SDK.Test.E2E; +namespace GitHub.Copilot.Test.E2E; /// /// Smoke coverage for the Copilot CLI built-in tools (bash, view, edit, create_file, diff --git a/dotnet/test/E2E/ClientE2ETests.cs b/dotnet/test/E2E/ClientE2ETests.cs index b8885fb2d..4e1ea2ddc 100644 --- a/dotnet/test/E2E/ClientE2ETests.cs +++ b/dotnet/test/E2E/ClientE2ETests.cs @@ -1,11 +1,11 @@ -/*--------------------------------------------------------------------------------------------- +/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ -using GitHub.Copilot.SDK.Test.Harness; +using GitHub.Copilot.Test.Harness; using Xunit; -namespace GitHub.Copilot.SDK.Test.E2E; +namespace GitHub.Copilot.Test.E2E; // These tests bypass E2ETestBase because they are about how the CLI subprocess is started // Other test classes should instead inherit from E2ETestBase @@ -16,19 +16,16 @@ public class ClientE2ETests [InlineData(false)] // TCP transport public async Task Should_Start_And_Connect_To_Server(bool useStdio) { - using var client = new CopilotClient(new CopilotClientOptions { UseStdio = useStdio }); + using var client = new CopilotClient(new CopilotClientOptions { Connection = useStdio ? RuntimeConnection.ForStdio() : RuntimeConnection.ForTcp() }); try { await client.StartAsync(); - Assert.Equal(ConnectionState.Connected, client.State); - var pong = await client.PingAsync("test message"); Assert.Equal("pong: test message", pong.Message); Assert.NotEqual(default, pong.Timestamp); await client.StopAsync(); - Assert.Equal(ConnectionState.Disconnected, client.State); } finally { @@ -41,12 +38,10 @@ public async Task Should_Start_And_Connect_To_Server(bool useStdio) [InlineData(false)] // TCP transport public async Task Should_Force_Stop_Without_Cleanup(bool useStdio) { - using var client = new CopilotClient(new CopilotClientOptions { UseStdio = useStdio }); + using var client = new CopilotClient(new CopilotClientOptions { Connection = useStdio ? RuntimeConnection.ForStdio() : RuntimeConnection.ForTcp() }); await client.CreateSessionAsync(new SessionConfig { OnPermissionRequest = PermissionHandler.ApproveAll }); await client.ForceStopAsync(); - - Assert.Equal(ConnectionState.Disconnected, client.State); } [Theory] @@ -54,7 +49,7 @@ public async Task Should_Force_Stop_Without_Cleanup(bool useStdio) [InlineData(false)] // TCP transport public async Task Should_Get_Status_With_Version_And_Protocol_Info(bool useStdio) { - using var client = new CopilotClient(new CopilotClientOptions { UseStdio = useStdio }); + using var client = new CopilotClient(new CopilotClientOptions { Connection = useStdio ? RuntimeConnection.ForStdio() : RuntimeConnection.ForTcp() }); try { @@ -78,7 +73,7 @@ public async Task Should_Get_Status_With_Version_And_Protocol_Info(bool useStdio [InlineData(false)] // TCP transport public async Task Should_Get_Auth_Status(bool useStdio) { - using var client = new CopilotClient(new CopilotClientOptions { UseStdio = useStdio }); + using var client = new CopilotClient(new CopilotClientOptions { Connection = useStdio ? RuntimeConnection.ForStdio() : RuntimeConnection.ForTcp() }); try { @@ -105,7 +100,7 @@ public async Task Should_Get_Auth_Status(bool useStdio) [InlineData(false)] // TCP transport public async Task Should_List_Models_When_Authenticated(bool useStdio) { - using var client = new CopilotClient(new CopilotClientOptions { UseStdio = useStdio }); + using var client = new CopilotClient(new CopilotClientOptions { Connection = useStdio ? RuntimeConnection.ForStdio() : RuntimeConnection.ForTcp() }); try { @@ -143,7 +138,7 @@ public async Task Should_List_Models_When_Authenticated(bool useStdio) [InlineData(false)] // TCP transport public async Task Should_Not_Throw_When_Disposing_Session_After_Stopping_Client(bool useStdio) { - await using var client = new CopilotClient(new CopilotClientOptions { UseStdio = useStdio }); + await using var client = new CopilotClient(new CopilotClientOptions { Connection = useStdio ? RuntimeConnection.ForStdio() : RuntimeConnection.ForTcp() }); await using var session = await client.CreateSessionAsync(new SessionConfig { OnPermissionRequest = PermissionHandler.ApproveAll }); await client.StopAsync(); @@ -156,8 +151,9 @@ public async Task Should_Report_Error_With_Stderr_When_CLI_Fails_To_Start(bool u { var client = new CopilotClient(new CopilotClientOptions { - CliArgs = ["--nonexistent-flag-for-testing"], - UseStdio = useStdio + Connection = useStdio + ? RuntimeConnection.ForStdio(args: ["--nonexistent-flag-for-testing"]) + : RuntimeConnection.ForTcp(args: ["--nonexistent-flag-for-testing"]) }); var ex = await Assert.ThrowsAsync(() => client.StartAsync()); @@ -184,7 +180,7 @@ public async Task Should_Report_Error_With_Stderr_When_CLI_Fails_To_Start(bool u [InlineData(false)] // TCP transport public async Task Should_Allow_CreateSession_Called_Without_PermissionHandler(bool useStdio) { - await using var client = new CopilotClient(new CopilotClientOptions { UseStdio = useStdio }); + await using var client = new CopilotClient(new CopilotClientOptions { Connection = useStdio ? RuntimeConnection.ForStdio() : RuntimeConnection.ForTcp() }); await using var session = await client.CreateSessionAsync(new SessionConfig()); Assert.NotNull(session.SessionId); @@ -196,19 +192,18 @@ public async Task Should_Allow_ResumeSession_Called_Without_PermissionHandler() const string connectionToken = "client-e2e-resume-token"; await using var ctx = await E2ETestContext.CreateAsync(); - await using var client = ctx.CreateClient(useStdio: false, options: new CopilotClientOptions + await using var client = ctx.CreateClient(options: new CopilotClientOptions { - TcpConnectionToken = connectionToken, + Connection = RuntimeConnection.ForTcp(connectionToken: connectionToken), }); await using var originalSession = await client.CreateSessionAsync(new SessionConfig()); - var port = client.ActualPort + var port = client.RuntimePort ?? throw new InvalidOperationException("Client must be using TCP transport to support multi-client resume."); await using var resumeClient = ctx.CreateClient(options: new CopilotClientOptions { - CliUrl = $"localhost:{port}", - TcpConnectionToken = connectionToken, + Connection = RuntimeConnection.ForUri($"localhost:{port}", connectionToken: connectionToken), }); await using var resumedSession = await resumeClient.ResumeSessionAsync(originalSession.SessionId, new()); @@ -237,7 +232,7 @@ public async Task ListModels_WithCustomHandler_CallsHandler(bool useStdio) var callCount = 0; await using var client = new CopilotClient(new CopilotClientOptions { - UseStdio = useStdio, + Connection = useStdio ? RuntimeConnection.ForStdio() : RuntimeConnection.ForTcp(), OnListModels = (ct) => { callCount++; @@ -274,7 +269,7 @@ public async Task ListModels_WithCustomHandler_CachesResults(bool useStdio) var callCount = 0; await using var client = new CopilotClient(new CopilotClientOptions { - UseStdio = useStdio, + Connection = useStdio ? RuntimeConnection.ForStdio() : RuntimeConnection.ForTcp(), OnListModels = (ct) => { callCount++; @@ -310,7 +305,7 @@ public async Task ListModels_WithCustomHandler_WorksWithoutStart(bool useStdio) var callCount = 0; await using var client = new CopilotClient(new CopilotClientOptions { - UseStdio = useStdio, + Connection = useStdio ? RuntimeConnection.ForStdio() : RuntimeConnection.ForTcp(), OnListModels = (ct) => { callCount++; diff --git a/dotnet/test/E2E/ClientLifecycleE2ETests.cs b/dotnet/test/E2E/ClientLifecycleE2ETests.cs index 7026093f8..4b09c695d 100644 --- a/dotnet/test/E2E/ClientLifecycleE2ETests.cs +++ b/dotnet/test/E2E/ClientLifecycleE2ETests.cs @@ -1,12 +1,12 @@ -/*--------------------------------------------------------------------------------------------- +/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ -using GitHub.Copilot.SDK.Rpc; +using GitHub.Copilot.Rpc; using Xunit; using Xunit.Abstractions; -namespace GitHub.Copilot.SDK.Test.E2E; +namespace GitHub.Copilot.Test.E2E; public class ClientLifecycleE2ETests(E2ETestFixture fixture, ITestOutputHelper output) : E2ETestBase(fixture, "client_lifecycle", output) @@ -15,9 +15,9 @@ public class ClientLifecycleE2ETests(E2ETestFixture fixture, ITestOutputHelper o public async Task Should_Receive_Session_Created_Lifecycle_Event() { var created = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - using var subscription = Client.On(evt => + using var subscription = Client.OnLifecycle(evt => { - if (evt.Type == SessionLifecycleEventTypes.Created) + if (evt is SessionCreatedEvent) { created.TrySetResult(evt); } @@ -26,7 +26,7 @@ public async Task Should_Receive_Session_Created_Lifecycle_Event() await using var session = await CreateSessionAsync(); var evt = await created.Task.WaitAsync(TimeSpan.FromSeconds(10)); - Assert.Equal(SessionLifecycleEventTypes.Created, evt.Type); + Assert.IsType(evt); Assert.Equal(session.SessionId, evt.SessionId); } @@ -34,12 +34,12 @@ public async Task Should_Receive_Session_Created_Lifecycle_Event() public async Task Should_Filter_Session_Lifecycle_Events_By_Type() { var created = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - using var subscription = Client.On(SessionLifecycleEventTypes.Created, evt => created.TrySetResult(evt)); + using var subscription = Client.OnLifecycle(evt => created.TrySetResult(evt)); await using var session = await CreateSessionAsync(); var evt = await created.Task.WaitAsync(TimeSpan.FromSeconds(10)); - Assert.Equal(SessionLifecycleEventTypes.Created, evt.Type); + Assert.IsType(evt); Assert.Equal(session.SessionId, evt.SessionId); } @@ -48,9 +48,9 @@ public async Task Disposing_Lifecycle_Subscription_Stops_Receiving_Events() { var count = 0; var created = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - var subscription = Client.On(_ => Interlocked.Increment(ref count)); + var subscription = Client.OnLifecycle(_ => Interlocked.Increment(ref count)); subscription.Dispose(); - using var activeSubscription = Client.On(SessionLifecycleEventTypes.Created, evt => created.TrySetResult(evt)); + using var activeSubscription = Client.OnLifecycle(evt => created.TrySetResult(evt)); await using var session = await CreateSessionAsync(); var evt = await created.Task.WaitAsync(TimeSpan.FromSeconds(10)); @@ -66,9 +66,6 @@ public async Task Dispose_Disconnects_Client_And_Disposes_Rpc_Surface(bool useAs { var client = Ctx.CreateClient(); await client.StartAsync(); - - Assert.Equal(ConnectionState.Connected, client.State); - if (useAsyncDispose) { await client.DisposeAsync(); @@ -77,8 +74,6 @@ public async Task Dispose_Disconnects_Client_And_Disposes_Rpc_Surface(bool useAs { client.Dispose(); } - - Assert.Equal(ConnectionState.Disconnected, client.State); Assert.Throws(() => client.Rpc); } @@ -88,7 +83,7 @@ public async Task Should_Receive_Session_Updated_Lifecycle_Event_For_Non_Ephemer await using var session = await CreateSessionAsync(); var updated = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - using var subscription = Client.On(SessionLifecycleEventTypes.Updated, evt => + using var subscription = Client.OnLifecycle(evt => { if (string.Equals(evt.SessionId, session.SessionId, StringComparison.Ordinal)) { @@ -101,7 +96,7 @@ public async Task Should_Receive_Session_Updated_Lifecycle_Event_For_Non_Ephemer await session.Rpc.Mode.SetAsync(SessionMode.Plan); var evt = await updated.Task.WaitAsync(TimeSpan.FromSeconds(15)); - Assert.Equal(SessionLifecycleEventTypes.Updated, evt.Type); + Assert.IsType(evt); Assert.Equal(session.SessionId, evt.SessionId); } @@ -118,7 +113,7 @@ public async Task Should_Receive_Session_Deleted_Lifecycle_Event_When_Deleted() await session.SendAndWaitAsync(new MessageOptions { Prompt = "Say SESSION_DELETED_OK exactly." }); var deleted = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - using var subscription = Client.On(SessionLifecycleEventTypes.Deleted, evt => + using var subscription = Client.OnLifecycle(evt => { if (string.Equals(evt.SessionId, sessionId, StringComparison.Ordinal)) { @@ -132,7 +127,7 @@ public async Task Should_Receive_Session_Deleted_Lifecycle_Event_When_Deleted() await Client.DeleteSessionAsync(sessionId); var evt = await deleted.Task.WaitAsync(TimeSpan.FromSeconds(15)); - Assert.Equal(SessionLifecycleEventTypes.Deleted, evt.Type); + Assert.IsType(evt); Assert.Equal(sessionId, evt.SessionId); await session.DisposeAsync(); diff --git a/dotnet/test/E2E/ClientOptionsE2ETests.cs b/dotnet/test/E2E/ClientOptionsE2ETests.cs index af17205c0..142f46abb 100644 --- a/dotnet/test/E2E/ClientOptionsE2ETests.cs +++ b/dotnet/test/E2E/ClientOptionsE2ETests.cs @@ -1,4 +1,4 @@ -/*--------------------------------------------------------------------------------------------- +/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ @@ -10,52 +10,20 @@ using Xunit; using Xunit.Abstractions; -namespace GitHub.Copilot.SDK.Test.E2E; +namespace GitHub.Copilot.Test.E2E; public class ClientOptionsE2ETests(E2ETestFixture fixture, ITestOutputHelper output) : E2ETestBase(fixture, "client_options", output) { - [Fact] - public async Task AutoStart_False_Requires_Explicit_Start() - { - await using var client = Ctx.CreateClient(options: new CopilotClientOptions - { - AutoStart = false, - }); - - Assert.Equal(ConnectionState.Disconnected, client.State); - - var ex = await Assert.ThrowsAsync(() => - client.CreateSessionAsync(new SessionConfig { OnPermissionRequest = PermissionHandler.ApproveAll })); - Assert.Contains("StartAsync", ex.Message, StringComparison.Ordinal); - - await client.StartAsync(); - Assert.Equal(ConnectionState.Connected, client.State); - - var session = await client.CreateSessionAsync(new SessionConfig - { - OnPermissionRequest = PermissionHandler.ApproveAll, - }); - Assert.Matches(@"^[a-f0-9-]+$", session.SessionId); - - await session.DisposeAsync(); - } - [Fact] public async Task Should_Listen_On_Configured_Tcp_Port() { var port = GetAvailableTcpPort(); await using var client = Ctx.CreateClient( - useStdio: false, - options: new CopilotClientOptions - { - Port = port, - }); + options: new CopilotClientOptions { Connection = RuntimeConnection.ForTcp(port: port) }); await client.StartAsync(); - - Assert.Equal(ConnectionState.Connected, client.State); - Assert.Equal(port, client.ActualPort); + Assert.Equal(port, client.RuntimePort); var response = await client.PingAsync("fixed-port"); Assert.Equal("pong: fixed-port", response.Message); @@ -70,7 +38,7 @@ public async Task Should_Use_Client_Cwd_For_Default_WorkingDirectory() await using var client = Ctx.CreateClient(options: new CopilotClientOptions { - Cwd = clientCwd, + WorkingDirectory = clientCwd, }); var session = await client.CreateSessionAsync(new SessionConfig @@ -101,13 +69,11 @@ public async Task Should_Propagate_Process_Options_To_Spawned_Cli() await using var client = Ctx.CreateClient(options: new CopilotClientOptions { - AutoStart = false, - CliPath = cliPath, - CliArgs = ["--capture-file", capturePath], - CopilotHome = copilotHomeFromOption, + Connection = RuntimeConnection.ForStdio(path: cliPath, args: ["--capture-file", capturePath]), + BaseDirectory = copilotHomeFromOption, Environment = clientEnv, GitHubToken = "process-option-token", - LogLevel = "debug", + LogLevel = CopilotLogLevel.Debug, SessionIdleTimeoutSeconds = 17, Telemetry = new TelemetryConfig { @@ -165,9 +131,7 @@ public async Task Should_Forward_EnableSessionTelemetry_In_Wire_Request() await using var client = Ctx.CreateClient(options: new CopilotClientOptions { - AutoStart = false, - CliPath = cliPath, - CliArgs = ["--capture-file", capturePath], + Connection = RuntimeConnection.ForStdio(path: cliPath, args: ["--capture-file", capturePath]), UseLoggedInUser = false, }); @@ -194,9 +158,7 @@ public async Task Should_Omit_EnableSessionTelemetry_When_Not_Set() await using var client = Ctx.CreateClient(options: new CopilotClientOptions { - AutoStart = false, - CliPath = cliPath, - CliArgs = ["--capture-file", capturePath], + Connection = RuntimeConnection.ForStdio(path: cliPath, args: ["--capture-file", capturePath]), UseLoggedInUser = false, }); @@ -222,9 +184,7 @@ public async Task Should_Propagate_Activity_TraceContext_To_Session_Create_And_S await using var client = Ctx.CreateClient(options: new CopilotClientOptions { - AutoStart = false, - CliPath = cliPath, - CliArgs = ["--capture-file", capturePath], + Connection = RuntimeConnection.ForStdio(path: cliPath, args: ["--capture-file", capturePath]), UseLoggedInUser = false, }); @@ -266,11 +226,9 @@ public async Task ForceStop_Does_Not_Rethrow_When_Tcp_Cli_Drops_During_Startup() await File.WriteAllTextAsync(cliPath, FakeTcpDropDuringStartupCliScript); await using var client = Ctx.CreateClient( - useStdio: false, options: new CopilotClientOptions { - AutoStart = false, - CliPath = cliPath, + Connection = RuntimeConnection.ForTcp(path: cliPath), UseLoggedInUser = false, }); @@ -278,7 +236,6 @@ public async Task ForceStop_Does_Not_Rethrow_When_Tcp_Cli_Drops_During_Startup() Assert.Contains("Communication error", ex.Message, StringComparison.Ordinal); await client.ForceStopAsync(); - Assert.Equal(ConnectionState.Disconnected, client.State); } [Fact] @@ -290,12 +247,9 @@ public async Task StartAsync_Cleans_Up_Tcp_Cli_Process_When_Connect_Fails() await File.WriteAllTextAsync(cliPath, FakeTcpUnavailablePortCliScript); await using var client = Ctx.CreateClient( - useStdio: false, options: new CopilotClientOptions { - AutoStart = false, - CliPath = cliPath, - CliArgs = ["--pid-file", pidPath, "--announce-port", unavailablePort.ToString(CultureInfo.InvariantCulture)], + Connection = RuntimeConnection.ForTcp(path: cliPath, args: ["--pid-file", pidPath, "--announce-port", unavailablePort.ToString(CultureInfo.InvariantCulture)]), UseLoggedInUser = false, }); @@ -305,7 +259,6 @@ public async Task StartAsync_Cleans_Up_Tcp_Cli_Process_When_Connect_Fails() await AssertProcessExitedAsync(pid); await client.ForceStopAsync(); - Assert.Equal(ConnectionState.Disconnected, client.State); } [Fact] @@ -315,9 +268,7 @@ public async Task Should_Propagate_Activity_TraceContext_To_Session_Resume() await using var client = Ctx.CreateClient(options: new CopilotClientOptions { - AutoStart = false, - CliPath = cliPath, - CliArgs = ["--capture-file", capturePath], + Connection = RuntimeConnection.ForStdio(path: cliPath, args: ["--capture-file", capturePath]), UseLoggedInUser = false, }); @@ -385,28 +336,20 @@ public void Should_Allow_Explicit_UseLoggedInUser_True_With_GitHubToken() } [Fact] - public void Should_Throw_When_GitHubToken_Used_With_CliUrl() + public void Should_Throw_When_GitHubToken_Used_With_UriConnection() { Assert.Throws(() => { - _ = new CopilotClient(new CopilotClientOptions - { - CliUrl = "localhost:8080", - GitHubToken = "gho_test_token" - }); + _ = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri("localhost:8080"), GitHubToken = "gho_test_token" }); }); } [Fact] - public void Should_Throw_When_UseLoggedInUser_Used_With_CliUrl() + public void Should_Throw_When_UseLoggedInUser_Used_With_UriConnection() { Assert.Throws(() => { - _ = new CopilotClient(new CopilotClientOptions - { - CliUrl = "localhost:8080", - UseLoggedInUser = false - }); + _ = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri("localhost:8080"), UseLoggedInUser = false }); }); } diff --git a/dotnet/test/E2E/ClientSessionManagementE2ETests.cs b/dotnet/test/E2E/ClientSessionManagementE2ETests.cs index f2d54a1d5..961b0e028 100644 --- a/dotnet/test/E2E/ClientSessionManagementE2ETests.cs +++ b/dotnet/test/E2E/ClientSessionManagementE2ETests.cs @@ -5,7 +5,7 @@ using Xunit; using Xunit.Abstractions; -namespace GitHub.Copilot.SDK.Test.E2E; +namespace GitHub.Copilot.Test.E2E; public class ClientSessionManagementE2ETests(E2ETestFixture fixture, ITestOutputHelper output) : E2ETestBase(fixture, "client_api", output) @@ -47,6 +47,14 @@ public async Task Should_Get_Null_Last_Session_Id_Before_Any_Sessions_Exist() { await Client.StartAsync(); + // Other tests in this class create sessions, and xUnit doesn't guarantee + // test execution order. Clear any leftover sessions so this test sees a + // genuinely empty state regardless of order. + foreach (var existing in await Client.ListSessionsAsync()) + { + await Client.DeleteSessionAsync(existing.SessionId); + } + var result = await Client.GetLastSessionIdAsync(); Assert.Null(result); diff --git a/dotnet/test/E2E/CommandsE2ETests.cs b/dotnet/test/E2E/CommandsE2ETests.cs index fd5e2165a..60a62bd58 100644 --- a/dotnet/test/E2E/CommandsE2ETests.cs +++ b/dotnet/test/E2E/CommandsE2ETests.cs @@ -5,7 +5,7 @@ using Xunit; using Xunit.Abstractions; -namespace GitHub.Copilot.SDK.Test.E2E; +namespace GitHub.Copilot.Test.E2E; public class CommandsE2ETests(E2ETestFixture fixture, ITestOutputHelper output) : E2ETestBase(fixture, "commands", output) diff --git a/dotnet/test/E2E/CompactionE2ETests.cs b/dotnet/test/E2E/CompactionE2ETests.cs index abee92219..63d467535 100644 --- a/dotnet/test/E2E/CompactionE2ETests.cs +++ b/dotnet/test/E2E/CompactionE2ETests.cs @@ -2,11 +2,11 @@ * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ -using GitHub.Copilot.SDK.Test.Harness; +using GitHub.Copilot.Test.Harness; using Xunit; using Xunit.Abstractions; -namespace GitHub.Copilot.SDK.Test.E2E; +namespace GitHub.Copilot.Test.E2E; public class CompactionE2ETests(E2ETestFixture fixture, ITestOutputHelper output) : E2ETestBase(fixture, "compaction", output) { @@ -85,7 +85,7 @@ public async Task Should_Not_Emit_Compaction_Events_When_Infinite_Sessions_Disab var compactionEvents = new List(); - session.On(evt => + session.On(evt => { if (evt is SessionCompactionStartEvent or SessionCompactionCompleteEvent) { diff --git a/dotnet/test/E2E/ElicitationE2ETests.cs b/dotnet/test/E2E/ElicitationE2ETests.cs index ca2714402..c14e11d55 100644 --- a/dotnet/test/E2E/ElicitationE2ETests.cs +++ b/dotnet/test/E2E/ElicitationE2ETests.cs @@ -1,12 +1,12 @@ -/*--------------------------------------------------------------------------------------------- +/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ -using GitHub.Copilot.SDK.Rpc; +using GitHub.Copilot.Rpc; using Xunit; using Xunit.Abstractions; -namespace GitHub.Copilot.SDK.Test.E2E; +namespace GitHub.Copilot.Test.E2E; public class ElicitationE2ETests(E2ETestFixture fixture, ITestOutputHelper output) : E2ETestBase(fixture, "elicitation", output) @@ -56,7 +56,7 @@ public async Task Elicitation_Throws_When_Capability_Is_Missing() ex = await Assert.ThrowsAsync(async () => { - await session.Ui.ElicitationAsync(new ElicitationParams + await session.Ui.ElicitAsync(new ElicitationParams { Message = "Enter name", RequestedSchema = new ElicitationSchema @@ -199,7 +199,7 @@ public async Task InputAsync_Returns_Freeform_Value() }, }); - var result = await session.Ui.InputAsync("Enter value", new InputOptions + var result = await session.Ui.InputAsync("Enter value", new UiInputOptions { Title = "Value", Description = "A value to test", @@ -246,9 +246,9 @@ public async Task ElicitationAsync_Returns_All_Action_Shapes() }, }; - var accept = await session.Ui.ElicitationAsync(parameters); - var decline = await session.Ui.ElicitationAsync(parameters); - var cancel = await session.Ui.ElicitationAsync(parameters); + var accept = await session.Ui.ElicitAsync(parameters); + var decline = await session.Ui.ElicitAsync(parameters); + var cancel = await session.Ui.ElicitAsync(parameters); Assert.Equal(UIElicitationResponseAction.Accept, accept.Action); Assert.Equal("Mona", accept.Content!["name"].ToString()); @@ -333,7 +333,7 @@ public void ElicitationResult_Types_Are_Properly_Structured() [Fact] public void InputOptions_Has_All_Properties() { - var options = new InputOptions + var options = new UiInputOptions { Title = "Email Address", Description = "Enter your email", @@ -381,7 +381,7 @@ public void ElicitationContext_Has_All_Properties() [Fact] public async Task Session_Config_OnElicitationRequest_Is_Cloned() { - ElicitationHandler handler = _ => Task.FromResult(new ElicitationResult + Func> handler = _ => Task.FromResult(new ElicitationResult { Action = UIElicitationResponseAction.Cancel, }); @@ -400,7 +400,7 @@ public async Task Session_Config_OnElicitationRequest_Is_Cloned() [Fact] public void Resume_Config_OnElicitationRequest_Is_Cloned() { - ElicitationHandler handler = _ => Task.FromResult(new ElicitationResult + Func> handler = _ => Task.FromResult(new ElicitationResult { Action = UIElicitationResponseAction.Cancel, }); diff --git a/dotnet/test/E2E/ErrorResilienceE2ETests.cs b/dotnet/test/E2E/ErrorResilienceE2ETests.cs index 4899f1386..ab69e8c43 100644 --- a/dotnet/test/E2E/ErrorResilienceE2ETests.cs +++ b/dotnet/test/E2E/ErrorResilienceE2ETests.cs @@ -1,11 +1,11 @@ -/*--------------------------------------------------------------------------------------------- +/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ using Xunit; using Xunit.Abstractions; -namespace GitHub.Copilot.SDK.Test.E2E; +namespace GitHub.Copilot.Test.E2E; /// /// Verifies the SDK's behavior at the edges of the session lifecycle: sending or @@ -32,7 +32,7 @@ public async Task Should_Throw_When_Getting_Messages_From_Disconnected_Session() var session = await CreateSessionAsync(); await session.DisposeAsync(); - await Assert.ThrowsAnyAsync(() => session.GetMessagesAsync()); + await Assert.ThrowsAnyAsync(() => session.GetEventsAsync()); } [Fact] diff --git a/dotnet/test/E2E/EventFidelityE2ETests.cs b/dotnet/test/E2E/EventFidelityE2ETests.cs index fa034c565..4504984f9 100644 --- a/dotnet/test/E2E/EventFidelityE2ETests.cs +++ b/dotnet/test/E2E/EventFidelityE2ETests.cs @@ -1,12 +1,12 @@ -/*--------------------------------------------------------------------------------------------- +/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ -using GitHub.Copilot.SDK.Test.Harness; +using GitHub.Copilot.Test.Harness; using Xunit; using Xunit.Abstractions; -namespace GitHub.Copilot.SDK.Test.E2E; +namespace GitHub.Copilot.Test.E2E; /// /// Verifies the shape and ordering of s emitted from the @@ -25,7 +25,7 @@ public async Task Should_Emit_Events_In_Correct_Order_For_Tool_Using_Conversatio var session = await CreateSessionAsync(); var events = new List(); - session.On(evt => { lock (events) { events.Add(evt); } }); + session.On(evt => { lock (events) { events.Add(evt); } }); await session.SendAndWaitAsync(new MessageOptions { @@ -55,7 +55,7 @@ public async Task Should_Include_Valid_Fields_On_All_Events() { var session = await CreateSessionAsync(); var events = new List(); - session.On(evt => { lock (events) { events.Add(evt); } }); + session.On(evt => { lock (events) { events.Add(evt); } }); await session.SendAndWaitAsync(new MessageOptions { @@ -91,7 +91,7 @@ public async Task Should_Emit_Assistant_Usage_Event_After_Model_Call() { var session = await CreateSessionAsync(); var events = new List(); - session.On(evt => { lock (events) { events.Add(evt); } }); + session.On(evt => { lock (events) { events.Add(evt); } }); await session.SendAndWaitAsync(new MessageOptions { @@ -114,7 +114,7 @@ public async Task Should_Emit_Session_Usage_Info_Event_After_Model_Call() { var session = await CreateSessionAsync(); var events = new List(); - session.On(evt => { lock (events) { events.Add(evt); } }); + session.On(evt => { lock (events) { events.Add(evt); } }); await session.SendAndWaitAsync(new MessageOptions { @@ -163,7 +163,7 @@ public async Task Should_Emit_Tool_Execution_Events_With_Correct_Fields() var session = await CreateSessionAsync(); var events = new List(); - session.On(evt => { lock (events) { events.Add(evt); } }); + session.On(evt => { lock (events) { events.Add(evt); } }); await session.SendAndWaitAsync(new MessageOptions { @@ -194,7 +194,7 @@ public async Task Should_Emit_Assistant_Message_With_MessageId() { var session = await CreateSessionAsync(); var events = new List(); - session.On(evt => { lock (events) { events.Add(evt); } }); + session.On(evt => { lock (events) { events.Add(evt); } }); await session.SendAndWaitAsync(new MessageOptions { @@ -225,7 +225,7 @@ await session.SendAndWaitAsync(new MessageOptions Prompt = "Read the file 'order.txt' and tell me what the number is.", }); - var messages = await session.GetMessagesAsync(); + var messages = await session.GetEventsAsync(); var types = messages.Select(m => m.Type).ToList(); // Verify complete event ordering contract: diff --git a/dotnet/test/E2E/HookLifecycleAndOutputE2ETests.cs b/dotnet/test/E2E/HookLifecycleAndOutputE2ETests.cs index 6bb589391..b16ee2b56 100644 --- a/dotnet/test/E2E/HookLifecycleAndOutputE2ETests.cs +++ b/dotnet/test/E2E/HookLifecycleAndOutputE2ETests.cs @@ -3,10 +3,11 @@ *--------------------------------------------------------------------------------------------*/ using Microsoft.Extensions.AI; +using System.Text.Json; using Xunit; using Xunit.Abstractions; -namespace GitHub.Copilot.SDK.Test.E2E; +namespace GitHub.Copilot.Test.E2E; /// /// E2E coverage for every handler exposed on : @@ -43,7 +44,7 @@ public async Task Should_Invoke_OnSessionStart_Hook_On_New_Session() Assert.NotEmpty(sessionStartInputs); Assert.Equal("new", sessionStartInputs[0].Source); - Assert.True(sessionStartInputs[0].Timestamp > 0); + Assert.True(sessionStartInputs[0].Timestamp > DateTimeOffset.UnixEpoch); Assert.False(string.IsNullOrEmpty(sessionStartInputs[0].Cwd)); await session.DisposeAsync(); @@ -71,7 +72,7 @@ public async Task Should_Invoke_OnUserPromptSubmitted_Hook_When_Sending_A_Messag Assert.NotEmpty(userPromptInputs); Assert.Contains("Say hello", userPromptInputs[0].Prompt); - Assert.True(userPromptInputs[0].Timestamp > 0); + Assert.True(userPromptInputs[0].Timestamp > DateTimeOffset.UnixEpoch); Assert.False(string.IsNullOrEmpty(userPromptInputs[0].Cwd)); await session.DisposeAsync(); @@ -116,7 +117,7 @@ public async Task Should_Invoke_OnErrorOccurred_Hook_When_Error_Occurs() OnErrorOccurred = (input, invocation) => { Assert.Equal(session!.SessionId, invocation.SessionId); - Assert.True(input.Timestamp > 0); + Assert.True(input.Timestamp > DateTimeOffset.UnixEpoch); Assert.False(string.IsNullOrEmpty(input.Cwd)); Assert.False(string.IsNullOrEmpty(input.Error)); Assert.Contains(input.ErrorContext, ValidErrorContexts); diff --git a/dotnet/test/E2E/HooksE2ETests.cs b/dotnet/test/E2E/HooksE2ETests.cs index 28301bf25..ab971c26e 100644 --- a/dotnet/test/E2E/HooksE2ETests.cs +++ b/dotnet/test/E2E/HooksE2ETests.cs @@ -2,11 +2,11 @@ * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ -using GitHub.Copilot.SDK.Test.Harness; +using GitHub.Copilot.Test.Harness; using Xunit; using Xunit.Abstractions; -namespace GitHub.Copilot.SDK.Test.E2E; +namespace GitHub.Copilot.Test.E2E; public class HooksE2ETests(E2ETestFixture fixture, ITestOutputHelper output) : E2ETestBase(fixture, "hooks", output) { diff --git a/dotnet/test/E2E/InMemorySessionFsSqliteHandler.cs b/dotnet/test/E2E/InMemorySessionFsSqliteHandler.cs index 2bfc4b1d8..0ae9c9a7d 100644 --- a/dotnet/test/E2E/InMemorySessionFsSqliteHandler.cs +++ b/dotnet/test/E2E/InMemorySessionFsSqliteHandler.cs @@ -1,13 +1,13 @@ -/*--------------------------------------------------------------------------------------------- +/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ using System.Collections.Concurrent; -using GitHub.Copilot.SDK; -using GitHub.Copilot.SDK.Rpc; +using GitHub.Copilot; +using GitHub.Copilot.Rpc; using Microsoft.Data.Sqlite; -namespace GitHub.Copilot.SDK.Test.E2E; +namespace GitHub.Copilot.Test.E2E; internal record SqliteCall(string SessionId, string QueryType, string Query); @@ -174,19 +174,19 @@ protected override Task StatAsync(string path, Cancellation throw new FileNotFoundException($"Path does not exist: {path}"); } - protected override Task MkdirAsync(string path, bool recursive, int? mode, CancellationToken cancellationToken) + protected override Task MakeDirectoryAsync(string path, bool recursive, int? mode, CancellationToken cancellationToken) { _directories[Resolve(path)] = 0; return Task.CompletedTask; } - protected override Task> ReaddirAsync(string path, CancellationToken cancellationToken) + protected override Task> ReadDirectoryAsync(string path, CancellationToken cancellationToken) => Task.FromResult>([]); - protected override Task> ReaddirWithTypesAsync(string path, CancellationToken cancellationToken) + protected override Task> ReadDirectoryWithTypesAsync(string path, CancellationToken cancellationToken) => Task.FromResult>([]); - protected override Task RmAsync(string path, bool recursive, bool force, CancellationToken cancellationToken) + protected override Task RemoveAsync(string path, bool recursive, bool force, CancellationToken cancellationToken) { var key = Resolve(path); Files.TryRemove(key, out _); diff --git a/dotnet/test/E2E/ModeHandlersE2ETests.cs b/dotnet/test/E2E/ModeHandlersE2ETests.cs index 0af54c4fd..061967a3b 100644 --- a/dotnet/test/E2E/ModeHandlersE2ETests.cs +++ b/dotnet/test/E2E/ModeHandlersE2ETests.cs @@ -1,12 +1,12 @@ -/*--------------------------------------------------------------------------------------------- +/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ -using GitHub.Copilot.SDK.Test.Harness; +using GitHub.Copilot.Test.Harness; using Xunit; using Xunit.Abstractions; -namespace GitHub.Copilot.SDK.Test.E2E; +namespace GitHub.Copilot.Test.E2E; public class ModeHandlersE2ETests(E2ETestFixture fixture, ITestOutputHelper output) : E2ETestBase(fixture, "mode_handlers", output) @@ -28,7 +28,7 @@ public async Task Should_Invoke_Exit_Plan_Mode_Handler_When_Model_Uses_Tool() { GitHubToken = Token, OnPermissionRequest = PermissionHandler.ApproveAll, - OnExitPlanMode = (request, invocation) => + OnExitPlanModeRequest = (request, invocation) => { handlerTask.TrySetResult((request, invocation)); return Task.FromResult(new ExitPlanModeResult @@ -96,7 +96,7 @@ public async Task Should_Invoke_Auto_Mode_Switch_Handler_When_Rate_Limited() { GitHubToken = Token, OnPermissionRequest = PermissionHandler.ApproveAll, - OnAutoModeSwitch = (request, invocation) => + OnAutoModeSwitchRequest = (request, invocation) => { handlerTask.TrySetResult((request, invocation)); return Task.FromResult(AutoModeSwitchResponse.Yes); @@ -176,7 +176,7 @@ private static async Task GetNextEventOfTypeAllowingRateLimitAsync( var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); using var cts = new CancellationTokenSource(timeout ?? TimeSpan.FromSeconds(30)); - using var subscription = session.On(evt => + using var subscription = session.On(evt => { if (evt is T matched && predicate(matched)) { diff --git a/dotnet/test/E2E/MultiClientCommandsElicitationE2ETests.cs b/dotnet/test/E2E/MultiClientCommandsElicitationE2ETests.cs index 5d70f51b1..d60c21709 100644 --- a/dotnet/test/E2E/MultiClientCommandsElicitationE2ETests.cs +++ b/dotnet/test/E2E/MultiClientCommandsElicitationE2ETests.cs @@ -1,12 +1,12 @@ -/*--------------------------------------------------------------------------------------------- +/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ -using GitHub.Copilot.SDK.Test.Harness; +using GitHub.Copilot.Test.Harness; using Xunit; using Xunit.Abstractions; -namespace GitHub.Copilot.SDK.Test.E2E; +namespace GitHub.Copilot.Test.E2E; /// /// Custom fixture for multi-client commands/elicitation tests. @@ -22,9 +22,9 @@ public class MultiClientCommandsElicitationFixture : IAsyncLifetime public async Task InitializeAsync() { Ctx = await E2ETestContext.CreateAsync(); - Client1 = Ctx.CreateClient(useStdio: false, options: new CopilotClientOptions + Client1 = Ctx.CreateClient(options: new CopilotClientOptions { - TcpConnectionToken = SharedToken, + Connection = RuntimeConnection.ForTcp(connectionToken: SharedToken), }, persistent: true); } @@ -65,13 +65,12 @@ public async Task InitializeAsync() }); await initSession.DisposeAsync(); - var port = Client1.ActualPort - ?? throw new InvalidOperationException("Client1 is not using TCP mode; ActualPort is null"); + var port = Client1.RuntimePort + ?? throw new InvalidOperationException("Client1 is not using TCP mode; RuntimePort is null"); _client2 = Ctx.CreateClient(options: new CopilotClientOptions { - CliUrl = $"localhost:{port}", - TcpConnectionToken = MultiClientCommandsElicitationFixture.SharedToken, + Connection = RuntimeConnection.ForUri($"localhost:{port}", connectionToken: MultiClientCommandsElicitationFixture.SharedToken), }); } @@ -112,7 +111,7 @@ public async Task Client_Receives_Commands_Changed_When_Another_Client_Joins_Wit var commandsChangedTcs = new TaskCompletionSource( TaskCreationOptions.RunContinuationsAsynchronously); - using var sub = session1.On(evt => + using var sub = session1.On(evt => { if (evt is CommandsChangedEvent changed) { @@ -133,7 +132,7 @@ public async Task Client_Receives_Commands_Changed_When_Another_Client_Joins_Wit Handler = _ => Task.CompletedTask, }, ], - DisableResume = true, + SuppressResumeEvent = true, }); var commandsChanged = await commandsChangedTcs.Task.WaitAsync(TimeSpan.FromSeconds(15)); @@ -160,7 +159,7 @@ public async Task Capabilities_Changed_Fires_When_Second_Client_Joins_With_Elici var capChangedTcs = new TaskCompletionSource( TaskCreationOptions.RunContinuationsAsynchronously); - using var sub = session1.On(evt => + using var sub = session1.On(evt => { if (evt is CapabilitiesChangedEvent capEvt) { @@ -177,7 +176,7 @@ public async Task Capabilities_Changed_Fires_When_Second_Client_Joins_With_Elici Action = Rpc.UIElicitationResponseAction.Accept, Content = new Dictionary(), }), - DisableResume = true, + SuppressResumeEvent = true, }); var capEvent = await capChangedTcs.Task.WaitAsync(TimeSpan.FromSeconds(15)); @@ -206,7 +205,7 @@ public async Task Capabilities_Changed_Fires_When_Elicitation_Provider_Disconnec var capEnabledTcs = new TaskCompletionSource( TaskCreationOptions.RunContinuationsAsynchronously); - using var subEnabled = session1.On(evt => + using var subEnabled = session1.On(evt => { if (evt is CapabilitiesChangedEvent { Data.Ui.Elicitation: true }) { @@ -215,12 +214,11 @@ public async Task Capabilities_Changed_Fires_When_Elicitation_Provider_Disconnec }); // Use a dedicated client (client3) so we can stop it without affecting client2 - var port = Client1.ActualPort - ?? throw new InvalidOperationException("Client1 ActualPort is null"); + var port = Client1.RuntimePort + ?? throw new InvalidOperationException("Client1 RuntimePort is null"); _client3 = Ctx.CreateClient(options: new CopilotClientOptions { - CliUrl = $"localhost:{port}", - TcpConnectionToken = MultiClientCommandsElicitationFixture.SharedToken, + Connection = RuntimeConnection.ForUri($"localhost:{port}", connectionToken: MultiClientCommandsElicitationFixture.SharedToken), }); // Client3 joins WITH elicitation handler @@ -232,7 +230,7 @@ public async Task Capabilities_Changed_Fires_When_Elicitation_Provider_Disconnec Action = Rpc.UIElicitationResponseAction.Accept, Content = new Dictionary(), }), - DisableResume = true, + SuppressResumeEvent = true, }); await capEnabledTcs.Task.WaitAsync(TimeSpan.FromSeconds(15)); @@ -242,7 +240,7 @@ public async Task Capabilities_Changed_Fires_When_Elicitation_Provider_Disconnec var capDisabledTcs = new TaskCompletionSource( TaskCreationOptions.RunContinuationsAsynchronously); - using var subDisabled = session1.On(evt => + using var subDisabled = session1.On(evt => { if (evt is CapabilitiesChangedEvent { Data.Ui.Elicitation: false }) { diff --git a/dotnet/test/E2E/MultiClientE2ETests.cs b/dotnet/test/E2E/MultiClientE2ETests.cs index bd939a6cf..34efd09b2 100644 --- a/dotnet/test/E2E/MultiClientE2ETests.cs +++ b/dotnet/test/E2E/MultiClientE2ETests.cs @@ -2,14 +2,14 @@ * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ -using GitHub.Copilot.SDK.Test.Harness; +using GitHub.Copilot.Test.Harness; using Microsoft.Extensions.AI; using System.Collections.Concurrent; using System.ComponentModel; using Xunit; using Xunit.Abstractions; -namespace GitHub.Copilot.SDK.Test.E2E; +namespace GitHub.Copilot.Test.E2E; /// /// Custom fixture for multi-client tests that uses TCP mode so a second client can connect. @@ -24,9 +24,9 @@ public class MultiClientTestFixture : IAsyncLifetime public async Task InitializeAsync() { Ctx = await E2ETestContext.CreateAsync(); - Client1 = Ctx.CreateClient(useStdio: false, options: new CopilotClientOptions + Client1 = Ctx.CreateClient(options: new CopilotClientOptions { - TcpConnectionToken = SharedToken, + Connection = RuntimeConnection.ForTcp(connectionToken: SharedToken), }, persistent: true); } @@ -63,13 +63,12 @@ public async Task InitializeAsync() }); await initSession.DisposeAsync(); - var port = Client1.ActualPort - ?? throw new InvalidOperationException("Client1 is not using TCP mode; ActualPort is null"); + var port = Client1.RuntimePort + ?? throw new InvalidOperationException("Client1 is not using TCP mode; RuntimePort is null"); _client2 = Ctx.CreateClient(options: new CopilotClientOptions { - CliUrl = $"localhost:{port}", - TcpConnectionToken = MultiClientTestFixture.SharedToken, + Connection = RuntimeConnection.ForUri($"localhost:{port}", connectionToken: MultiClientTestFixture.SharedToken), }); } @@ -113,12 +112,12 @@ public async Task Both_Clients_See_Tool_Request_And_Completion_Events() var client1Completed = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var client2Completed = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - using var sub1 = session1.On(evt => + using var sub1 = session1.On(evt => { if (evt is ExternalToolRequestedEvent) client1Requested.TrySetResult(true); if (evt is ExternalToolCompletedEvent) client1Completed.TrySetResult(true); }); - using var sub2 = session2.On(evt => + using var sub2 = session2.On(evt => { if (evt is ExternalToolRequestedEvent) client2Requested.TrySetResult(true); if (evt is ExternalToolCompletedEvent) client2Completed.TrySetResult(true); @@ -173,8 +172,8 @@ public async Task One_Client_Approves_Permission_And_Both_See_The_Result() var client1PermissionCompleted = TestHelper.GetNextEventOfTypeAsync(session1); var client2PermissionCompleted = TestHelper.GetNextEventOfTypeAsync(session2); - using var sub1 = session1.On(evt => client1Events.Add(evt)); - using var sub2 = session2.On(evt => client2Events.Add(evt)); + using var sub1 = session1.On(evt => client1Events.Add(evt)); + using var sub2 = session2.On(evt => client2Events.Add(evt)); await session1.SendAsync(new MessageOptions { @@ -223,8 +222,8 @@ public async Task One_Client_Rejects_Permission_And_Both_See_The_Result() // Wait for PermissionCompletedEvent on client2 which may arrive slightly after session1 goes idle var client2PermissionCompleted = TestHelper.GetNextEventOfTypeAsync(session2); - using var sub1 = session1.On(evt => client1Events.Add(evt)); - using var sub2 = session2.On(evt => client2Events.Add(evt)); + using var sub1 = session1.On(evt => client1Events.Add(evt)); + using var sub2 = session2.On(evt => client2Events.Add(evt)); // Write a file so the agent has something to edit await File.WriteAllTextAsync(Path.Combine(Ctx.WorkDir, "protected.txt"), "protected content"); @@ -331,11 +330,10 @@ public async Task Disconnecting_Client_Removes_Its_Tools() await Client2.ForceStopAsync(); // Recreate client2 for cleanup - var port = Client1.ActualPort!.Value; + var port = Client1.RuntimePort!.Value; _client2 = Ctx.CreateClient(options: new CopilotClientOptions { - CliUrl = $"localhost:{port}", - TcpConnectionToken = MultiClientTestFixture.SharedToken, + Connection = RuntimeConnection.ForUri($"localhost:{port}", connectionToken: MultiClientTestFixture.SharedToken), }); // Now only stable_tool should be available diff --git a/dotnet/test/E2E/MultiTurnE2ETests.cs b/dotnet/test/E2E/MultiTurnE2ETests.cs index b10acfbc2..4cfff92d4 100644 --- a/dotnet/test/E2E/MultiTurnE2ETests.cs +++ b/dotnet/test/E2E/MultiTurnE2ETests.cs @@ -5,7 +5,7 @@ using Xunit; using Xunit.Abstractions; -namespace GitHub.Copilot.SDK.Test.E2E; +namespace GitHub.Copilot.Test.E2E; /// /// Verifies that information produced in one turn (e.g., the contents of a file @@ -23,7 +23,7 @@ public async Task Should_Use_Tool_Results_From_Previous_Turns() var session = await CreateSessionAsync(); var events = new List(); var eventsLock = new object(); - using var subscription = session.On(evt => + using var subscription = session.On(evt => { lock (eventsLock) { @@ -52,7 +52,7 @@ public async Task Should_Handle_File_Creation_Then_Reading_Across_Turns() var session = await CreateSessionAsync(); var events = new List(); var eventsLock = new object(); - using var subscription = session.On(evt => + using var subscription = session.On(evt => { lock (eventsLock) { diff --git a/dotnet/test/E2E/PendingWorkResumeE2ETests.cs b/dotnet/test/E2E/PendingWorkResumeE2ETests.cs index f78ba0d70..889dc0050 100644 --- a/dotnet/test/E2E/PendingWorkResumeE2ETests.cs +++ b/dotnet/test/E2E/PendingWorkResumeE2ETests.cs @@ -1,15 +1,15 @@ -/*--------------------------------------------------------------------------------------------- +/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ -using GitHub.Copilot.SDK.Test.Harness; +using GitHub.Copilot.Test.Harness; using Microsoft.Extensions.AI; using System.ComponentModel; using Xunit; using Xunit.Abstractions; -using RpcPermissionDecisionApproveOnce = GitHub.Copilot.SDK.Rpc.PermissionDecisionApproveOnce; +using RpcPermissionDecisionApproveOnce = GitHub.Copilot.Rpc.PermissionDecisionApproveOnce; -namespace GitHub.Copilot.SDK.Test.E2E; +namespace GitHub.Copilot.Test.E2E; public class PendingWorkResumeE2ETests(E2ETestFixture fixture, ITestOutputHelper output) : E2ETestBase(fixture, "pending_work_resume", output) @@ -24,11 +24,11 @@ public async Task Should_Continue_Pending_Permission_Request_After_Resume() var releaseOriginalPermission = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var resumedToolInvoked = false; - await using var server = Ctx.CreateClient(useStdio: false, options: new CopilotClientOptions { TcpConnectionToken = SharedToken }); + await using var server = Ctx.CreateClient(options: new CopilotClientOptions { Connection = RuntimeConnection.ForTcp(connectionToken: SharedToken) }); await server.StartAsync(); var cliUrl = GetCliUrl(server); - using var suspendedClient = Ctx.CreateClient(options: new CopilotClientOptions { CliUrl = cliUrl, TcpConnectionToken = SharedToken }); + using var suspendedClient = Ctx.CreateClient(options: new CopilotClientOptions { Connection = RuntimeConnection.ForUri(cliUrl, connectionToken: SharedToken) }); var session1 = await suspendedClient.CreateSessionAsync(new SessionConfig { Tools = [AIFunctionFactory.Create(ResumePermissionTool, "resume_permission_tool")], @@ -55,7 +55,7 @@ await session1.SendAsync(new MessageOptions await suspendedClient.ForceStopAsync(); - await using var resumedTcpClient = Ctx.CreateClient(options: new CopilotClientOptions { CliUrl = cliUrl, TcpConnectionToken = SharedToken }); + await using var resumedTcpClient = Ctx.CreateClient(options: new CopilotClientOptions { Connection = RuntimeConnection.ForUri(cliUrl, connectionToken: SharedToken) }); var session2 = await resumedTcpClient.ResumeSessionAsync(sessionId, new ResumeSessionConfig { ContinuePendingWork = true, @@ -107,11 +107,11 @@ public async Task Should_Continue_Pending_External_Tool_Request_After_Resume() var originalToolStarted = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var releaseOriginalTool = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - await using var server = Ctx.CreateClient(useStdio: false, options: new CopilotClientOptions { TcpConnectionToken = SharedToken }); + await using var server = Ctx.CreateClient(options: new CopilotClientOptions { Connection = RuntimeConnection.ForTcp(connectionToken: SharedToken) }); await server.StartAsync(); var cliUrl = GetCliUrl(server); - using var suspendedClient = Ctx.CreateClient(options: new CopilotClientOptions { CliUrl = cliUrl, TcpConnectionToken = SharedToken }); + using var suspendedClient = Ctx.CreateClient(options: new CopilotClientOptions { Connection = RuntimeConnection.ForUri(cliUrl, connectionToken: SharedToken) }); var session1 = await suspendedClient.CreateSessionAsync(new SessionConfig { Tools = [AIFunctionFactory.Create(BlockingExternalTool, "resume_external_tool")], @@ -132,7 +132,7 @@ await session1.SendAsync(new MessageOptions Assert.Equal("beta", await originalToolStarted.Task.WaitAsync(PendingWorkTimeout)); await suspendedClient.ForceStopAsync(); - await using var resumedClient = Ctx.CreateClient(options: new CopilotClientOptions { CliUrl = cliUrl, TcpConnectionToken = SharedToken }); + await using var resumedClient = Ctx.CreateClient(options: new CopilotClientOptions { Connection = RuntimeConnection.ForUri(cliUrl, connectionToken: SharedToken) }); var session2 = await resumedClient.ResumeSessionAsync(sessionId, new ResumeSessionConfig { ContinuePendingWork = true, @@ -171,11 +171,11 @@ public async Task Should_Keep_Pending_External_Tool_Handleable_On_Warm_Resume_Wh var releaseOriginalTool = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var invocationCount = 0; - await using var server = Ctx.CreateClient(useStdio: false, options: new CopilotClientOptions { TcpConnectionToken = SharedToken }); + await using var server = Ctx.CreateClient(options: new CopilotClientOptions { Connection = RuntimeConnection.ForTcp(connectionToken: SharedToken) }); await server.StartAsync(); var cliUrl = GetCliUrl(server); - using var suspendedClient = Ctx.CreateClient(options: new CopilotClientOptions { CliUrl = cliUrl, TcpConnectionToken = SharedToken }); + using var suspendedClient = Ctx.CreateClient(options: new CopilotClientOptions { Connection = RuntimeConnection.ForUri(cliUrl, connectionToken: SharedToken) }); var session1 = await suspendedClient.CreateSessionAsync(new SessionConfig { Tools = [AIFunctionFactory.Create(BlockingExternalTool, "resume_external_tool")], @@ -197,7 +197,7 @@ await session1.SendAsync(new MessageOptions await suspendedClient.ForceStopAsync(); - await using var resumedClient = Ctx.CreateClient(options: new CopilotClientOptions { CliUrl = cliUrl, TcpConnectionToken = SharedToken }); + await using var resumedClient = Ctx.CreateClient(options: new CopilotClientOptions { Connection = RuntimeConnection.ForUri(cliUrl, connectionToken: SharedToken) }); var session2 = await resumedClient.ResumeSessionAsync(sessionId, new ResumeSessionConfig { ContinuePendingWork = false, @@ -242,11 +242,11 @@ public async Task Should_Continue_Parallel_Pending_External_Tool_Requests_After_ var releaseOriginalToolA = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var releaseOriginalToolB = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - await using var server = Ctx.CreateClient(useStdio: false, options: new CopilotClientOptions { TcpConnectionToken = SharedToken }); + await using var server = Ctx.CreateClient(options: new CopilotClientOptions { Connection = RuntimeConnection.ForTcp(connectionToken: SharedToken) }); await server.StartAsync(); var cliUrl = GetCliUrl(server); - using var suspendedClient = Ctx.CreateClient(options: new CopilotClientOptions { CliUrl = cliUrl, TcpConnectionToken = SharedToken }); + using var suspendedClient = Ctx.CreateClient(options: new CopilotClientOptions { Connection = RuntimeConnection.ForUri(cliUrl, connectionToken: SharedToken) }); var session1 = await suspendedClient.CreateSessionAsync(new SessionConfig { Tools = @@ -276,7 +276,7 @@ await Task.WhenAll( await suspendedClient.ForceStopAsync(); - await using var resumedClient = Ctx.CreateClient(options: new CopilotClientOptions { CliUrl = cliUrl, TcpConnectionToken = SharedToken }); + await using var resumedClient = Ctx.CreateClient(options: new CopilotClientOptions { Connection = RuntimeConnection.ForUri(cliUrl, connectionToken: SharedToken) }); var session2 = await resumedClient.ResumeSessionAsync(sessionId, new ResumeSessionConfig { ContinuePendingWork = true, @@ -321,12 +321,12 @@ async Task BlockingToolB([Description("Value to look up")] string value) [Fact] public async Task Should_Resume_Successfully_When_No_Pending_Work_Exists() { - await using var server = Ctx.CreateClient(useStdio: false, options: new CopilotClientOptions { TcpConnectionToken = SharedToken }); + await using var server = Ctx.CreateClient(options: new CopilotClientOptions { Connection = RuntimeConnection.ForTcp(connectionToken: SharedToken) }); await server.StartAsync(); var cliUrl = GetCliUrl(server); string sessionId; - await using (var firstClient = Ctx.CreateClient(options: new CopilotClientOptions { CliUrl = cliUrl, TcpConnectionToken = SharedToken })) + await using (var firstClient = Ctx.CreateClient(options: new CopilotClientOptions { Connection = RuntimeConnection.ForUri(cliUrl, connectionToken: SharedToken) })) { var firstSession = await firstClient.CreateSessionAsync(new SessionConfig { @@ -340,7 +340,7 @@ public async Task Should_Resume_Successfully_When_No_Pending_Work_Exists() await firstSession.DisposeAsync(); } - await using var resumedClient = Ctx.CreateClient(options: new CopilotClientOptions { CliUrl = cliUrl, TcpConnectionToken = SharedToken }); + await using var resumedClient = Ctx.CreateClient(options: new CopilotClientOptions { Connection = RuntimeConnection.ForUri(cliUrl, connectionToken: SharedToken) }); var resumedSession = await resumedClient.ResumeSessionAsync(sessionId, new ResumeSessionConfig { ContinuePendingWork = true, @@ -359,12 +359,12 @@ public async Task Should_Resume_Successfully_When_No_Pending_Work_Exists() [Fact] public async Task Should_Report_ContinuePendingWork_True_In_Resume_Event() { - await using var server = Ctx.CreateClient(useStdio: false, options: new CopilotClientOptions { TcpConnectionToken = SharedToken }); + await using var server = Ctx.CreateClient(options: new CopilotClientOptions { Connection = RuntimeConnection.ForTcp(connectionToken: SharedToken) }); await server.StartAsync(); var cliUrl = GetCliUrl(server); string sessionId; - await using (var firstClient = Ctx.CreateClient(options: new CopilotClientOptions { CliUrl = cliUrl, TcpConnectionToken = SharedToken })) + await using (var firstClient = Ctx.CreateClient(options: new CopilotClientOptions { Connection = RuntimeConnection.ForUri(cliUrl, connectionToken: SharedToken) })) { var firstSession = await firstClient.CreateSessionAsync(new SessionConfig { @@ -381,7 +381,7 @@ public async Task Should_Report_ContinuePendingWork_True_In_Resume_Event() await firstSession.DisposeAsync(); } - await using var resumedClient = Ctx.CreateClient(options: new CopilotClientOptions { CliUrl = cliUrl, TcpConnectionToken = SharedToken }); + await using var resumedClient = Ctx.CreateClient(options: new CopilotClientOptions { Connection = RuntimeConnection.ForUri(cliUrl, connectionToken: SharedToken) }); var resumedSession = await resumedClient.ResumeSessionAsync(sessionId, new ResumeSessionConfig { ContinuePendingWork = true, @@ -420,7 +420,7 @@ private static async Task> WaitFo TaskCreationOptions.RunContinuationsAsynchronously); using var cts = new CancellationTokenSource(PendingWorkTimeout); - using var subscription = session.On(evt => + using var subscription = session.On(evt => { if (evt is ExternalToolRequestedEvent toolEvent && expected.Contains(toolEvent.Data.ToolName)) { @@ -444,14 +444,14 @@ private static async Task> WaitFo private static string GetCliUrl(CopilotClient client) { - var port = client.ActualPort + var port = client.RuntimePort ?? throw new InvalidOperationException("Expected the test server to be listening on a TCP port."); return $"localhost:{port}"; } private static async Task GetSingleResumeEventAsync(CopilotSession session) { - var messages = await session.GetMessagesAsync(); + var messages = await session.GetEventsAsync(); return Assert.Single(messages.OfType()); } } diff --git a/dotnet/test/E2E/PerSessionAuthE2ETests.cs b/dotnet/test/E2E/PerSessionAuthE2ETests.cs index f93da300c..42a433f9d 100644 --- a/dotnet/test/E2E/PerSessionAuthE2ETests.cs +++ b/dotnet/test/E2E/PerSessionAuthE2ETests.cs @@ -2,11 +2,11 @@ * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ -using GitHub.Copilot.SDK.Test.Harness; +using GitHub.Copilot.Test.Harness; using Xunit; using Xunit.Abstractions; -namespace GitHub.Copilot.SDK.Test.E2E; +namespace GitHub.Copilot.Test.E2E; public class PerSessionAuthE2ETests(E2ETestFixture fixture, ITestOutputHelper output) : E2ETestBase(fixture, "per-session-auth", output) { diff --git a/dotnet/test/E2E/PermissionE2ETests.cs b/dotnet/test/E2E/PermissionE2ETests.cs index 9dd7a549f..953ab1469 100644 --- a/dotnet/test/E2E/PermissionE2ETests.cs +++ b/dotnet/test/E2E/PermissionE2ETests.cs @@ -2,14 +2,14 @@ * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ -using GitHub.Copilot.SDK.Test.Harness; +using GitHub.Copilot.Test.Harness; using Microsoft.Extensions.AI; using System.Text.Json; using System.Text.Json.Serialization; using Xunit; using Xunit.Abstractions; -namespace GitHub.Copilot.SDK.Test.E2E; +namespace GitHub.Copilot.Test.E2E; public partial class PermissionE2ETests(E2ETestFixture fixture, ITestOutputHelper output) : E2ETestBase(fixture, "permissions", output) { @@ -100,7 +100,7 @@ public async Task Should_Deny_Permission_When_Handler_Returns_Denied() // for the reject decision, which lets us assert the decision was honored // — not merely that the operation didn't happen. var userRejectedToolCall = false; - session.On(evt => + session.On(evt => { if (evt is ToolExecutionCompleteEvent toolEvt && !toolEvt.Data.Success && @@ -139,7 +139,7 @@ public async Task Should_Deny_Tool_Operations_When_Handler_Explicitly_Denies() }); var permissionDenied = false; - session.On(evt => + session.On(evt => { if (evt is ToolExecutionCompleteEvent toolEvt && !toolEvt.Data.Success && @@ -266,7 +266,7 @@ public async Task Should_Deny_Tool_Operations_When_Handler_Explicitly_Denies_Aft }); var permissionDenied = false; - session2.On(evt => + session2.On(evt => { if (evt is ToolExecutionCompleteEvent toolEvt && !toolEvt.Data.Success && @@ -344,7 +344,7 @@ void AddLifecycleEvent(string phase, string? toolCallId) } }); - using var subscription = session.On(evt => + using var subscription = session.On(evt => { switch (evt) { @@ -442,7 +442,7 @@ public async Task Should_Handle_Concurrent_Permission_Requests_From_Parallel_Too } }); - session.On(evt => + session.On(evt => { if (evt is ToolExecutionCompleteEvent toolEvt) { @@ -554,7 +554,7 @@ public async Task Should_Short_Circuit_Permission_Handler_When_Set_Approve_All_E try { var toolCompleted = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - using var subscription = session.On(evt => + using var subscription = session.On(evt => { if (evt is ToolExecutionCompleteEvent done && done.Data.Success) { diff --git a/dotnet/test/E2E/RpcAdditionalEdgeCasesE2ETests.cs b/dotnet/test/E2E/RpcAdditionalEdgeCasesE2ETests.cs index 238299c02..241a978a9 100644 --- a/dotnet/test/E2E/RpcAdditionalEdgeCasesE2ETests.cs +++ b/dotnet/test/E2E/RpcAdditionalEdgeCasesE2ETests.cs @@ -2,12 +2,12 @@ * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ -using GitHub.Copilot.SDK.Rpc; -using GitHub.Copilot.SDK.Test.Harness; +using GitHub.Copilot.Rpc; +using GitHub.Copilot.Test.Harness; using Xunit; using Xunit.Abstractions; -namespace GitHub.Copilot.SDK.Test.E2E; +namespace GitHub.Copilot.Test.E2E; /// /// Targeted gap-filler tests for assorted RPC surface area where the previous suite covered diff --git a/dotnet/test/E2E/RpcAgentE2ETests.cs b/dotnet/test/E2E/RpcAgentE2ETests.cs index b64e858e4..cd60a2934 100644 --- a/dotnet/test/E2E/RpcAgentE2ETests.cs +++ b/dotnet/test/E2E/RpcAgentE2ETests.cs @@ -2,12 +2,12 @@ * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ -using GitHub.Copilot.SDK.Rpc; -using GitHub.Copilot.SDK.Test.Harness; +using GitHub.Copilot.Rpc; +using GitHub.Copilot.Test.Harness; using Xunit; using Xunit.Abstractions; -namespace GitHub.Copilot.SDK.Test.E2E; +namespace GitHub.Copilot.Test.E2E; public class RpcAgentE2ETests(E2ETestFixture fixture, ITestOutputHelper output) : E2ETestBase(fixture, "rpc_agents", output) diff --git a/dotnet/test/E2E/RpcEventSideEffectsE2ETests.cs b/dotnet/test/E2E/RpcEventSideEffectsE2ETests.cs index a363b5586..9622553d1 100644 --- a/dotnet/test/E2E/RpcEventSideEffectsE2ETests.cs +++ b/dotnet/test/E2E/RpcEventSideEffectsE2ETests.cs @@ -1,13 +1,13 @@ -/*--------------------------------------------------------------------------------------------- +/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ -using GitHub.Copilot.SDK.Rpc; -using GitHub.Copilot.SDK.Test.Harness; +using GitHub.Copilot.Rpc; +using GitHub.Copilot.Test.Harness; using Xunit; using Xunit.Abstractions; -namespace GitHub.Copilot.SDK.Test.E2E; +namespace GitHub.Copilot.Test.E2E; /// /// Verifies that session-scoped RPC calls emit the expected side-effect session events. @@ -140,7 +140,7 @@ public async Task Should_Emit_Snapshot_Rewind_Event_And_Remove_Events_On_Truncat // gates flushing on shouldSaveSession, which flips on the first user.message). await session.SendAndWaitAsync(new MessageOptions { Prompt = "Say SNAPSHOT_REWIND_TARGET exactly." }); - var messages = await session.GetMessagesAsync(); + var messages = await session.GetEventsAsync(); var userEvent = messages.OfType().FirstOrDefault() ?? throw new InvalidOperationException("Expected at least one user.message in persisted history"); var targetEventId = userEvent.Id.ToString(); @@ -161,7 +161,7 @@ public async Task Should_Emit_Snapshot_Rewind_Event_And_Remove_Events_On_Truncat Assert.Equal(truncateResult.EventsRemoved, (long)rewindEvent.Data.EventsRemoved); // Verify the truncated event is no longer in persisted history. - var messagesAfter = await session.GetMessagesAsync(); + var messagesAfter = await session.GetEventsAsync(); Assert.DoesNotContain(messagesAfter, e => e.Id == userEvent.Id); } @@ -172,7 +172,7 @@ public async Task Should_Allow_Session_Use_After_Truncate() await session.SendAndWaitAsync(new MessageOptions { Prompt = "Say SNAPSHOT_REWIND_TARGET exactly." }); - var messages = await session.GetMessagesAsync(); + var messages = await session.GetEventsAsync(); var userEvent = messages.OfType().FirstOrDefault() ?? throw new InvalidOperationException("Expected at least one user.message in persisted history"); diff --git a/dotnet/test/E2E/RpcExtensionsLoadedE2ETests.cs b/dotnet/test/E2E/RpcExtensionsLoadedE2ETests.cs index 4d4388b22..1219b14a4 100644 --- a/dotnet/test/E2E/RpcExtensionsLoadedE2ETests.cs +++ b/dotnet/test/E2E/RpcExtensionsLoadedE2ETests.cs @@ -2,14 +2,14 @@ * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ -using GitHub.Copilot.SDK.Rpc; -using GitHub.Copilot.SDK.Test.Harness; +using GitHub.Copilot.Rpc; +using GitHub.Copilot.Test.Harness; using System.Diagnostics; using Xunit; using Xunit.Abstractions; -using RpcExtension = GitHub.Copilot.SDK.Rpc.Extension; +using RpcExtension = GitHub.Copilot.Rpc.Extension; -namespace GitHub.Copilot.SDK.Test.E2E; +namespace GitHub.Copilot.Test.E2E; /// /// E2E coverage for the loaded-extensions code path in the runtime: when the @@ -57,7 +57,7 @@ private CopilotClient CreateExtensionsClient() { return Ctx.CreateClient(options: new CopilotClientOptions { - CliArgs = ["--yolo"], + Connection = RuntimeConnection.ForStdio(args: ["--yolo"]), Environment = ExtensionsEnabledEnvironment(), }); } diff --git a/dotnet/test/E2E/RpcMcpAndSkillsE2ETests.cs b/dotnet/test/E2E/RpcMcpAndSkillsE2ETests.cs index 03cab27de..a3d08a1cb 100644 --- a/dotnet/test/E2E/RpcMcpAndSkillsE2ETests.cs +++ b/dotnet/test/E2E/RpcMcpAndSkillsE2ETests.cs @@ -4,10 +4,10 @@ using Xunit; using Xunit.Abstractions; -using RpcSkill = GitHub.Copilot.SDK.Rpc.Skill; -using RpcSkillList = GitHub.Copilot.SDK.Rpc.SkillList; +using RpcSkill = GitHub.Copilot.Rpc.Skill; +using RpcSkillList = GitHub.Copilot.Rpc.SkillList; -namespace GitHub.Copilot.SDK.Test.E2E; +namespace GitHub.Copilot.Test.E2E; public class RpcMcpAndSkillsE2ETests(E2ETestFixture fixture, ITestOutputHelper output) : E2ETestBase(fixture, "rpc_mcp_and_skills", output) @@ -102,7 +102,7 @@ public async Task Should_List_Extensions() // preventing breakage from new gates (e.g., extension-permission-access). await using var yoloClient = Ctx.CreateClient(options: new CopilotClientOptions { - CliArgs = ["--yolo"], + Connection = RuntimeConnection.ForStdio(args: ["--yolo"]), }); await using var session = await yoloClient.CreateSessionAsync(new SessionConfig { @@ -188,7 +188,7 @@ public async Task Should_Report_Error_When_Extensions_Are_Not_Available() // preventing breakage from new gates (e.g., extension-permission-access). await using var yoloClient = Ctx.CreateClient(options: new CopilotClientOptions { - CliArgs = ["--yolo"], + Connection = RuntimeConnection.ForStdio(args: ["--yolo"]), }); await using var session = await yoloClient.CreateSessionAsync(new SessionConfig { diff --git a/dotnet/test/E2E/RpcMcpConfigE2ETests.cs b/dotnet/test/E2E/RpcMcpConfigE2ETests.cs index 179fc4828..d26e5535d 100644 --- a/dotnet/test/E2E/RpcMcpConfigE2ETests.cs +++ b/dotnet/test/E2E/RpcMcpConfigE2ETests.cs @@ -2,12 +2,12 @@ * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ -using GitHub.Copilot.SDK.Rpc; +using GitHub.Copilot.Rpc; using System.Text.Json; using Xunit; using Xunit.Abstractions; -namespace GitHub.Copilot.SDK.Test.E2E; +namespace GitHub.Copilot.Test.E2E; public class RpcMcpConfigE2ETests(E2ETestFixture fixture, ITestOutputHelper output) : E2ETestBase(fixture, "rpc_mcp_config", output) diff --git a/dotnet/test/E2E/RpcServerE2ETests.cs b/dotnet/test/E2E/RpcServerE2ETests.cs index b7d7e4624..39913060c 100644 --- a/dotnet/test/E2E/RpcServerE2ETests.cs +++ b/dotnet/test/E2E/RpcServerE2ETests.cs @@ -2,11 +2,11 @@ * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ -using GitHub.Copilot.SDK.Test.Harness; +using GitHub.Copilot.Test.Harness; using Xunit; using Xunit.Abstractions; -namespace GitHub.Copilot.SDK.Test.E2E; +namespace GitHub.Copilot.Test.E2E; public class RpcServerE2ETests(E2ETestFixture fixture, ITestOutputHelper output) : E2ETestBase(fixture, "rpc_server", output) diff --git a/dotnet/test/E2E/RpcSessionStateE2ETests.cs b/dotnet/test/E2E/RpcSessionStateE2ETests.cs index ff0e9bd8f..04ba27f54 100644 --- a/dotnet/test/E2E/RpcSessionStateE2ETests.cs +++ b/dotnet/test/E2E/RpcSessionStateE2ETests.cs @@ -1,13 +1,13 @@ -/*--------------------------------------------------------------------------------------------- +/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ -using GitHub.Copilot.SDK.Rpc; -using GitHub.Copilot.SDK.Test.Harness; +using GitHub.Copilot.Rpc; +using GitHub.Copilot.Test.Harness; using Xunit; using Xunit.Abstractions; -namespace GitHub.Copilot.SDK.Test.E2E; +namespace GitHub.Copilot.Test.E2E; public class RpcSessionStateE2ETests(E2ETestFixture fixture, ITestOutputHelper output) : E2ETestBase(fixture, "rpc_session_state", output) @@ -252,7 +252,7 @@ public async Task Should_Fork_Session_With_Persisted_Messages() var initialAnswer = await session.SendAndWaitAsync(new MessageOptions { Prompt = sourcePrompt }); Assert.Contains("FORK_SOURCE_ALPHA", initialAnswer?.Data.Content ?? string.Empty); - var sourceConversation = GetConversationMessages(await session.GetMessagesAsync()); + var sourceConversation = GetConversationMessages(await session.GetEventsAsync()); Assert.Contains(sourceConversation, message => message.Role == "user" && message.Content == sourcePrompt); Assert.Contains(sourceConversation, message => message.Role == "assistant" && message.Content.Contains("FORK_SOURCE_ALPHA", StringComparison.Ordinal)); @@ -261,16 +261,16 @@ public async Task Should_Fork_Session_With_Persisted_Messages() Assert.NotEqual(session.SessionId, fork.SessionId); await using var forkedSession = await ResumeSessionAsync(fork.SessionId); - var forkedConversation = GetConversationMessages(await forkedSession.GetMessagesAsync()); + var forkedConversation = GetConversationMessages(await forkedSession.GetEventsAsync()); Assert.Equal(sourceConversation, forkedConversation.Take(sourceConversation.Count)); var forkAnswer = await forkedSession.SendAndWaitAsync(new MessageOptions { Prompt = forkPrompt }); Assert.Contains("FORK_CHILD_BETA", forkAnswer?.Data.Content ?? string.Empty); - var sourceAfterFork = GetConversationMessages(await session.GetMessagesAsync()); + var sourceAfterFork = GetConversationMessages(await session.GetEventsAsync()); Assert.DoesNotContain(sourceAfterFork, message => message.Content == forkPrompt); - var forkAfterPrompt = GetConversationMessages(await forkedSession.GetMessagesAsync()); + var forkAfterPrompt = GetConversationMessages(await forkedSession.GetEventsAsync()); Assert.Contains(forkAfterPrompt, message => message.Role == "user" && message.Content == forkPrompt); Assert.Contains(forkAfterPrompt, message => message.Role == "assistant" && message.Content.Contains("FORK_CHILD_BETA", StringComparison.Ordinal)); } @@ -298,7 +298,7 @@ public async Task Should_Handle_Forking_Session_Without_Persisted_Events() Assert.NotEqual(session.SessionId, forkSessionId); await using var forkedSession = await ResumeSessionAsync(forkSessionId); - Assert.Empty(GetConversationMessages(await forkedSession.GetMessagesAsync())); + Assert.Empty(GetConversationMessages(await forkedSession.GetEventsAsync())); } [Fact] @@ -311,7 +311,7 @@ public async Task Should_Fork_Session_To_Event_Id_Excluding_Boundary_Event() await session.SendAndWaitAsync(new MessageOptions { Prompt = firstPrompt }); await session.SendAndWaitAsync(new MessageOptions { Prompt = secondPrompt }); - var sourceEvents = await session.GetMessagesAsync(); + var sourceEvents = await session.GetEventsAsync(); var secondUserEvent = sourceEvents .OfType() .FirstOrDefault(e => string.Equals(e.Data.Content, secondPrompt, StringComparison.Ordinal)) @@ -325,7 +325,7 @@ public async Task Should_Fork_Session_To_Event_Id_Excluding_Boundary_Event() Assert.NotEqual(session.SessionId, fork.SessionId); await using var forkedSession = await ResumeSessionAsync(fork.SessionId); - var forkedEvents = await forkedSession.GetMessagesAsync(); + var forkedEvents = await forkedSession.GetEventsAsync(); Assert.DoesNotContain(forkedEvents, e => e.Id == secondUserEvent.Id); var forkedConversation = GetConversationMessages(forkedEvents); diff --git a/dotnet/test/E2E/RpcShellAndFleetE2ETests.cs b/dotnet/test/E2E/RpcShellAndFleetE2ETests.cs index ee9ebb27d..a51fc7dae 100644 --- a/dotnet/test/E2E/RpcShellAndFleetE2ETests.cs +++ b/dotnet/test/E2E/RpcShellAndFleetE2ETests.cs @@ -1,13 +1,13 @@ -/*--------------------------------------------------------------------------------------------- +/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ -using GitHub.Copilot.SDK.Test.Harness; +using GitHub.Copilot.Test.Harness; using Microsoft.Extensions.AI; using Xunit; using Xunit.Abstractions; -namespace GitHub.Copilot.SDK.Test.E2E; +namespace GitHub.Copilot.Test.E2E; public class RpcShellAndFleetE2ETests(E2ETestFixture fixture, ITestOutputHelper output) : E2ETestBase(fixture, "rpc_shell_and_fleet", output) @@ -118,7 +118,7 @@ private static async Task> WaitForMessagesAsync( await TestHelper.WaitForConditionAsync( async () => { - messages = (await session.GetMessagesAsync()).ToList(); + messages = (await session.GetEventsAsync()).ToList(); return predicate(messages); }, timeout: TimeSpan.FromSeconds(120), diff --git a/dotnet/test/E2E/RpcShellEdgeCaseE2ETests.cs b/dotnet/test/E2E/RpcShellEdgeCaseE2ETests.cs index 6b7a9c5e0..b6036b7b8 100644 --- a/dotnet/test/E2E/RpcShellEdgeCaseE2ETests.cs +++ b/dotnet/test/E2E/RpcShellEdgeCaseE2ETests.cs @@ -2,12 +2,12 @@ * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ -using GitHub.Copilot.SDK.Rpc; -using GitHub.Copilot.SDK.Test.Harness; +using GitHub.Copilot.Rpc; +using GitHub.Copilot.Test.Harness; using Xunit; using Xunit.Abstractions; -namespace GitHub.Copilot.SDK.Test.E2E; +namespace GitHub.Copilot.Test.E2E; /// /// Targeted edge-case tests for the shell RPC API (shell.exec, shell.kill). diff --git a/dotnet/test/E2E/RpcTasksAndHandlersE2ETests.cs b/dotnet/test/E2E/RpcTasksAndHandlersE2ETests.cs index 6e8860964..2ed338129 100644 --- a/dotnet/test/E2E/RpcTasksAndHandlersE2ETests.cs +++ b/dotnet/test/E2E/RpcTasksAndHandlersE2ETests.cs @@ -2,12 +2,12 @@ * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ -using GitHub.Copilot.SDK.Rpc; -using GitHub.Copilot.SDK.Test.Harness; +using GitHub.Copilot.Rpc; +using GitHub.Copilot.Test.Harness; using Xunit; using Xunit.Abstractions; -namespace GitHub.Copilot.SDK.Test.E2E; +namespace GitHub.Copilot.Test.E2E; public class RpcTasksAndHandlersE2ETests(E2ETestFixture fixture, ITestOutputHelper output) : E2ETestBase(fixture, "rpc_tasks_and_handlers", output) @@ -101,7 +101,7 @@ await TestHelper.WaitForConditionAsync( Assert.Equal("general-purpose", task.AgentType); Assert.Equal("Reply with TASK_AGENT_DONE exactly.", task.Prompt); Assert.Equal("SDK background agent coverage", task.Description); - Assert.Equal(GitHub.Copilot.SDK.Rpc.TaskExecutionMode.Background, task.ExecutionMode); + Assert.Equal(GitHub.Copilot.Rpc.TaskExecutionMode.Background, task.ExecutionMode); Assert.False(task.CanPromoteToBackground.GetValueOrDefault()); Assert.NotEqual(default, task.StartedAt); @@ -114,8 +114,8 @@ await TestHelper.WaitForConditionAsync( task = await FindAgentTaskAsync(session, started.AgentId); return task?.LatestResponse?.Contains("TASK_AGENT_DONE", StringComparison.Ordinal) == true || task?.Result?.Contains("TASK_AGENT_DONE", StringComparison.Ordinal) == true - || task?.Status == GitHub.Copilot.SDK.Rpc.TaskStatus.Completed - || task?.Status == GitHub.Copilot.SDK.Rpc.TaskStatus.Failed; + || task?.Status == GitHub.Copilot.Rpc.TaskStatus.Completed + || task?.Status == GitHub.Copilot.Rpc.TaskStatus.Failed; }, timeout: TimeSpan.FromSeconds(60), timeoutMessage: $"Background agent task '{started.AgentId}' did not produce a final observable state."); @@ -123,7 +123,7 @@ await TestHelper.WaitForConditionAsync( Assert.NotNull(task); Assert.Contains("TASK_AGENT_DONE", task.LatestResponse ?? task.Result ?? string.Empty); - if (task.Status == GitHub.Copilot.SDK.Rpc.TaskStatus.Idle) + if (task.Status == GitHub.Copilot.Rpc.TaskStatus.Idle) { var cancel = await session.Rpc.Tasks.CancelAsync(started.AgentId); Assert.True(cancel.Cancelled); diff --git a/dotnet/test/E2E/SessionConfigE2ETests.cs b/dotnet/test/E2E/SessionConfigE2ETests.cs index 43d6681b7..64f5518fb 100644 --- a/dotnet/test/E2E/SessionConfigE2ETests.cs +++ b/dotnet/test/E2E/SessionConfigE2ETests.cs @@ -1,14 +1,14 @@ -/*--------------------------------------------------------------------------------------------- +/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ -using GitHub.Copilot.SDK.Rpc; -using GitHub.Copilot.SDK.Test.Harness; +using GitHub.Copilot.Rpc; +using GitHub.Copilot.Test.Harness; using System.Text.Json; using Xunit; using Xunit.Abstractions; -namespace GitHub.Copilot.SDK.Test.E2E; +namespace GitHub.Copilot.Test.E2E; public class SessionConfigE2ETests(E2ETestFixture fixture, ITestOutputHelper output) : E2ETestBase(fixture, "session_config", output) @@ -112,7 +112,7 @@ public async Task Should_Use_Custom_SessionId() Assert.Equal(requestedSessionId, session.SessionId); - var messages = await session.GetMessagesAsync(); + var messages = await session.GetEventsAsync(); var startEvent = Assert.IsType(messages[0]); Assert.Equal(requestedSessionId, startEvent.Data.SessionId); @@ -131,7 +131,7 @@ public async Task Should_Apply_ReasoningEffort_On_Session_Create() ReasoningEffort = "high", }); - var startEvent = Assert.Single((await session.GetMessagesAsync()).OfType()); + var startEvent = Assert.Single((await session.GetEventsAsync()).OfType()); Assert.Equal(reasoningModelId, startEvent.Data.SelectedModel); Assert.Equal("high", startEvent.Data.ReasoningEffort); @@ -153,7 +153,7 @@ public async Task Should_Apply_All_ReasoningEffort_Values_On_Session_Create(stri ReasoningEffort = effort, }); - var startEvent = Assert.Single((await session.GetMessagesAsync()).OfType()); + var startEvent = Assert.Single((await session.GetEventsAsync()).OfType()); Assert.Equal(reasoningModelId, startEvent.Data.SelectedModel); Assert.Equal(effort, startEvent.Data.ReasoningEffort); @@ -172,7 +172,7 @@ public async Task Should_Apply_ReasoningEffort_On_Session_Resume() ReasoningEffort = "high", }); - var resumeEvent = Assert.Single((await resumedSession.GetMessagesAsync()).OfType()); + var resumeEvent = Assert.Single((await resumedSession.GetEventsAsync()).OfType()); Assert.Equal(reasoningModelId, resumeEvent.Data.SelectedModel); Assert.Equal("high", resumeEvent.Data.ReasoningEffort); diff --git a/dotnet/test/E2E/SessionE2ETests.cs b/dotnet/test/E2E/SessionE2ETests.cs index e46581249..7711c86dc 100644 --- a/dotnet/test/E2E/SessionE2ETests.cs +++ b/dotnet/test/E2E/SessionE2ETests.cs @@ -1,16 +1,16 @@ -/*--------------------------------------------------------------------------------------------- +/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ -using GitHub.Copilot.SDK.Rpc; -using GitHub.Copilot.SDK.Test.Harness; +using GitHub.Copilot.Rpc; +using GitHub.Copilot.Test.Harness; using Microsoft.Extensions.AI; using System.Collections.Concurrent; using System.ComponentModel; using Xunit; using Xunit.Abstractions; -namespace GitHub.Copilot.SDK.Test.E2E; +namespace GitHub.Copilot.Test.E2E; public class SessionE2ETests(E2ETestFixture fixture, ITestOutputHelper output) : E2ETestBase(fixture, "session", output) { @@ -21,14 +21,14 @@ public async Task ShouldCreateAndDisconnectSessions() Assert.Matches(@"^[a-f0-9-]+$", session.SessionId); - var messages = await session.GetMessagesAsync(); + var messages = await session.GetEventsAsync(); Assert.NotEmpty(messages); var startEvent = Assert.IsType(messages[0]); Assert.Equal(session.SessionId, startEvent.Data.SessionId); await session.DisposeAsync(); - await Assert.ThrowsAsync(() => session.GetMessagesAsync()); + await Assert.ThrowsAsync(() => session.GetEventsAsync()); } [Fact] @@ -243,7 +243,7 @@ public async Task Should_Resume_A_Session_Using_A_New_Client() }); Assert.Equal(sessionId, session2.SessionId); - var messages = await session2.GetMessagesAsync(); + var messages = await session2.GetEventsAsync(); Assert.Contains(messages, m => m is UserMessageEvent); var resumeEvent = Assert.Single(messages.OfType()); Assert.True(resumeEvent.Data.ContinuePendingWork); @@ -284,7 +284,7 @@ await session.SendAsync(new MessageOptions await sessionIdleTask; // The session should still be alive and usable after abort - var messages = await session.GetMessagesAsync(); + var messages = await session.GetEventsAsync(); Assert.NotEmpty(messages); // Verify an abort event exists in messages @@ -324,7 +324,7 @@ public async Task Should_Receive_Session_Events() var concurrentCount = 0; var maxConcurrent = 0; - session.On(evt => + session.On(evt => { // Track concurrent handler invocations to verify serial dispatch. var current = Interlocked.Increment(ref concurrentCount); @@ -393,7 +393,7 @@ public async Task Send_Returns_Immediately_While_Events_Stream_In_Background() }); var events = new ConcurrentQueue(); - session.On(evt => events.Enqueue(evt.Type)); + session.On(evt => events.Enqueue(evt.Type)); // Use a slow command so we can verify SendAsync() returns before completion await session.SendAsync(new MessageOptions { Prompt = "Run 'sleep 2 && echo done'" }); @@ -415,7 +415,7 @@ public async Task SendAndWait_Blocks_Until_Session_Idle_And_Returns_Final_Assist var session = await CreateSessionAsync(); var events = new ConcurrentQueue(); - session.On(evt => events.Enqueue(evt.Type)); + session.On(evt => events.Enqueue(evt.Type)); var response = await session.SendAndWaitAsync(new MessageOptions { Prompt = "What is 2+2?" }); @@ -581,7 +581,7 @@ public async Task Should_Log_Messages_At_Various_Levels() var session = await CreateSessionAsync(); var events = new List(); var eventsLock = new object(); - session.On(evt => + session.On(evt => { lock (eventsLock) { @@ -640,7 +640,7 @@ public async Task Handler_Exception_Does_Not_Halt_Event_Delivery() var eventCount = 0; var gotIdle = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - session.On(evt => + session.On(evt => { eventCount++; @@ -666,7 +666,7 @@ public async Task DisposeAsync_From_Handler_Does_Not_Deadlock() var session = await CreateSessionAsync(); var disposed = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - session.On(evt => + session.On(evt => { if (evt is UserMessageEvent) { @@ -728,7 +728,7 @@ await session.SendAndWaitAsync(new MessageOptions ], }); - var userMessage = (await session.GetMessagesAsync()).OfType().Last(); + var userMessage = (await session.GetEventsAsync()).OfType().Last(); var attachment = Assert.IsType(Assert.Single(userMessage.Data.Attachments!)); Assert.Equal("attached-file.txt", attachment.DisplayName); Assert.Equal(filePath, attachment.Path); @@ -758,7 +758,7 @@ await session.SendAndWaitAsync(new MessageOptions ], }); - var userMessage = (await session.GetMessagesAsync()).OfType().Last(); + var userMessage = (await session.GetEventsAsync()).OfType().Last(); var attachment = Assert.IsType(Assert.Single(userMessage.Data.Attachments!)); Assert.Equal("attached-directory", attachment.DisplayName); Assert.Equal(directoryPath, attachment.Path); @@ -791,7 +791,7 @@ await session.SendAndWaitAsync(new MessageOptions ], }); - var userMessage = (await session.GetMessagesAsync()).OfType().Last(); + var userMessage = (await session.GetEventsAsync()).OfType().Last(); var attachment = Assert.IsType(Assert.Single(userMessage.Data.Attachments!)); Assert.Equal("selected-file.cs", attachment.DisplayName); Assert.Equal(filePath, attachment.FilePath); @@ -823,7 +823,7 @@ await session.SendAndWaitAsync(new MessageOptions ], }); - var userMessage = (await session.GetMessagesAsync()).OfType().Last(); + var userMessage = (await session.GetEventsAsync()).OfType().Last(); var attachment = Assert.IsType(Assert.Single(userMessage.Data.Attachments!)); Assert.Equal(1234, attachment.Number); Assert.Equal(UserMessageAttachmentGithubReferenceType.Issue, attachment.ReferenceType); @@ -843,7 +843,7 @@ await session.SendAndWaitAsync(new MessageOptions Mode = "plan", }); - var userMessage = (await session.GetMessagesAsync()).OfType().Last(); + var userMessage = (await session.GetEventsAsync()).OfType().Last(); Assert.Equal("Say mode ok.", userMessage.Data.Content); // The current runtime accepts the per-message mode option but does not echo it on user.message. Assert.Null(userMessage.Data.AgentMode); diff --git a/dotnet/test/E2E/SessionFsE2ETests.cs b/dotnet/test/E2E/SessionFsE2ETests.cs index 7649c160c..812fc8997 100644 --- a/dotnet/test/E2E/SessionFsE2ETests.cs +++ b/dotnet/test/E2E/SessionFsE2ETests.cs @@ -1,14 +1,14 @@ -/*--------------------------------------------------------------------------------------------- +/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ -using GitHub.Copilot.SDK.Rpc; -using GitHub.Copilot.SDK.Test.Harness; +using GitHub.Copilot.Rpc; +using GitHub.Copilot.Test.Harness; using Microsoft.Extensions.AI; using Xunit; using Xunit.Abstractions; -namespace GitHub.Copilot.SDK.Test.E2E; +namespace GitHub.Copilot.Test.E2E; public class SessionFsE2ETests(E2ETestFixture fixture, ITestOutputHelper output) : E2ETestBase(fixture, "session_fs", output) @@ -31,7 +31,7 @@ public async Task Should_Route_File_Operations_Through_The_Session_Fs_Provider() var session = await client.CreateSessionAsync(new SessionConfig { OnPermissionRequest = PermissionHandler.ApproveAll, - CreateSessionFsHandler = s => new TestSessionFsHandler(s.SessionId, providerRoot), + CreateSessionFsProvider = s => new TestSessionFsHandler(s.SessionId, providerRoot), }); var msg = await session.SendAndWaitAsync(new MessageOptions { Prompt = "What is 100 + 200?" }); @@ -61,7 +61,7 @@ public async Task Should_Load_Session_Data_From_Fs_Provider_On_Resume() var session1 = await client.CreateSessionAsync(new SessionConfig { OnPermissionRequest = PermissionHandler.ApproveAll, - CreateSessionFsHandler = createSessionFsHandler, + CreateSessionFsProvider = createSessionFsHandler, }); var sessionId = session1.SessionId; @@ -75,7 +75,7 @@ public async Task Should_Load_Session_Data_From_Fs_Provider_On_Resume() var session2 = await client.ResumeSessionAsync(sessionId, new ResumeSessionConfig { OnPermissionRequest = PermissionHandler.ApproveAll, - CreateSessionFsHandler = createSessionFsHandler, + CreateSessionFsProvider = createSessionFsHandler, }); var msg2 = await session2.SendAndWaitAsync(new MessageOptions { Prompt = "What is that times 3?" }); @@ -100,20 +100,18 @@ public async Task Should_Reject_SetProvider_When_Sessions_Already_Exist() _ = await client1.CreateSessionAsync(new SessionConfig { OnPermissionRequest = PermissionHandler.ApproveAll, - CreateSessionFsHandler = createSessionFsHandler, + CreateSessionFsProvider = createSessionFsHandler, }); - var port = client1.ActualPort - ?? throw new InvalidOperationException("Client1 is not using TCP mode; ActualPort is null"); + var port = client1.RuntimePort + ?? throw new InvalidOperationException("Client1 is not using TCP mode; RuntimePort is null"); var client2 = Ctx.CreateClient( - useStdio: false, options: new CopilotClientOptions { - CliUrl = $"localhost:{port}", - LogLevel = "error", + LogLevel = CopilotLogLevel.Error, SessionFs = SessionFsConfig, - TcpConnectionToken = "session-fs-shared-token", + Connection = RuntimeConnection.ForUri($"localhost:{port}", connectionToken: "session-fs-shared-token"), }); try @@ -324,7 +322,7 @@ public async Task Should_Map_Large_Output_Handling_Into_SessionFs() var session = await client.CreateSessionAsync(new SessionConfig { OnPermissionRequest = PermissionHandler.ApproveAll, - CreateSessionFsHandler = s => new TestSessionFsHandler(s.SessionId, providerRoot), + CreateSessionFsProvider = s => new TestSessionFsHandler(s.SessionId, providerRoot), Tools = [ AIFunctionFactory.Create(() => suppliedFileContent, "get_big_string", "Returns a large string") @@ -336,7 +334,7 @@ await session.SendAndWaitAsync(new MessageOptions Prompt = "Call the get_big_string tool and reply with the word DONE only.", }); - var messages = await session.GetMessagesAsync(); + var messages = await session.GetEventsAsync(); var toolResult = FindToolCallResult(messages, "get_big_string"); Assert.NotNull(toolResult); Assert.Contains($"{SessionFsConfig.SessionStatePath}/temp/", toolResult); @@ -366,11 +364,11 @@ public async Task Should_Succeed_With_Compaction_While_Using_SessionFs() var session = await client.CreateSessionAsync(new SessionConfig { OnPermissionRequest = PermissionHandler.ApproveAll, - CreateSessionFsHandler = s => new TestSessionFsHandler(s.SessionId, providerRoot), + CreateSessionFsProvider = s => new TestSessionFsHandler(s.SessionId, providerRoot), }); SessionCompactionCompleteEvent? compactionEvent = null; - using var _ = session.On(evt => + using var _ = session.On(evt => { if (evt is SessionCompactionCompleteEvent complete) { @@ -405,7 +403,7 @@ public async Task Should_Write_Workspace_Metadata_Via_SessionFs() var session = await client.CreateSessionAsync(new SessionConfig { OnPermissionRequest = PermissionHandler.ApproveAll, - CreateSessionFsHandler = s => new TestSessionFsHandler(s.SessionId, providerRoot), + CreateSessionFsProvider = s => new TestSessionFsHandler(s.SessionId, providerRoot), }); var msg = await session.SendAndWaitAsync(new MessageOptions { Prompt = "What is 7 * 8?" }); @@ -436,7 +434,7 @@ public async Task Should_Persist_Plan_Md_Via_SessionFs() var session = await client.CreateSessionAsync(new SessionConfig { OnPermissionRequest = PermissionHandler.ApproveAll, - CreateSessionFsHandler = s => new TestSessionFsHandler(s.SessionId, providerRoot), + CreateSessionFsProvider = s => new TestSessionFsHandler(s.SessionId, providerRoot), }); // Write a plan via the session RPC @@ -457,13 +455,16 @@ public async Task Should_Persist_Plan_Md_Via_SessionFs() private CopilotClient CreateSessionFsClient(string providerRoot, bool useStdio = true, string? tcpConnectionToken = null) { + RuntimeConnection connection = useStdio + ? RuntimeConnection.ForStdio() + : RuntimeConnection.ForTcp(connectionToken: tcpConnectionToken); + Directory.CreateDirectory(providerRoot); return Ctx.CreateClient( - useStdio: useStdio, options: new CopilotClientOptions { SessionFs = SessionFsConfig, - TcpConnectionToken = tcpConnectionToken, + Connection = connection, }); } @@ -593,16 +594,16 @@ protected override Task ExistsAsync(string path, CancellationToken cancell protected override Task StatAsync(string path, CancellationToken cancellationToken) => Task.FromException(exception); - protected override Task MkdirAsync(string path, bool recursive, int? mode, CancellationToken cancellationToken) => + protected override Task MakeDirectoryAsync(string path, bool recursive, int? mode, CancellationToken cancellationToken) => Task.FromException(exception); - protected override Task> ReaddirAsync(string path, CancellationToken cancellationToken) => + protected override Task> ReadDirectoryAsync(string path, CancellationToken cancellationToken) => Task.FromException>(exception); - protected override Task> ReaddirWithTypesAsync(string path, CancellationToken cancellationToken) => + protected override Task> ReadDirectoryWithTypesAsync(string path, CancellationToken cancellationToken) => Task.FromException>(exception); - protected override Task RmAsync(string path, bool recursive, bool force, CancellationToken cancellationToken) => + protected override Task RemoveAsync(string path, bool recursive, bool force, CancellationToken cancellationToken) => Task.FromException(exception); protected override Task RenameAsync(string src, string dest, CancellationToken cancellationToken) => @@ -674,13 +675,13 @@ protected override Task StatAsync(string path, Cancellation }); } - protected override Task MkdirAsync(string path, bool recursive, int? mode, CancellationToken cancellationToken) + protected override Task MakeDirectoryAsync(string path, bool recursive, int? mode, CancellationToken cancellationToken) { Directory.CreateDirectory(ResolvePath(path)); return Task.CompletedTask; } - protected override Task> ReaddirAsync(string path, CancellationToken cancellationToken) + protected override Task> ReadDirectoryAsync(string path, CancellationToken cancellationToken) { IList entries = Directory .EnumerateFileSystemEntries(ResolvePath(path)) @@ -691,7 +692,7 @@ protected override Task> ReaddirAsync(string path, CancellationTok return Task.FromResult(entries); } - protected override Task> ReaddirWithTypesAsync(string path, CancellationToken cancellationToken) + protected override Task> ReadDirectoryWithTypesAsync(string path, CancellationToken cancellationToken) { IList entries = Directory .EnumerateFileSystemEntries(ResolvePath(path)) @@ -704,7 +705,7 @@ protected override Task> ReaddirWithTypesA return Task.FromResult(entries); } - protected override Task RmAsync(string path, bool recursive, bool force, CancellationToken cancellationToken) + protected override Task RemoveAsync(string path, bool recursive, bool force, CancellationToken cancellationToken) { var fullPath = ResolvePath(path); diff --git a/dotnet/test/E2E/SessionFsSqliteE2ETests.cs b/dotnet/test/E2E/SessionFsSqliteE2ETests.cs index f495fca3d..9caa624af 100644 --- a/dotnet/test/E2E/SessionFsSqliteE2ETests.cs +++ b/dotnet/test/E2E/SessionFsSqliteE2ETests.cs @@ -1,13 +1,13 @@ -/*--------------------------------------------------------------------------------------------- +/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ -using GitHub.Copilot.SDK.Rpc; -using GitHub.Copilot.SDK.Test.Harness; +using GitHub.Copilot.Rpc; +using GitHub.Copilot.Test.Harness; using Xunit; using Xunit.Abstractions; -namespace GitHub.Copilot.SDK.Test.E2E; +namespace GitHub.Copilot.Test.E2E; public class SessionFsSqliteE2ETests(E2ETestFixture fixture, ITestOutputHelper output) : E2ETestBase(fixture, "session_fs_sqlite", output) @@ -30,7 +30,7 @@ public async Task Should_Route_Sql_Queries_Through_The_Sessionfs_Sqlite_Handler( var session = await client.CreateSessionAsync(new SessionConfig { OnPermissionRequest = PermissionHandler.ApproveAll, - CreateSessionFsHandler = s => new InMemorySessionFsSqliteHandler(s.SessionId, _sqliteCalls), + CreateSessionFsProvider = s => new InMemorySessionFsSqliteHandler(s.SessionId, _sqliteCalls), }); var msg = await session.SendAndWaitAsync(new MessageOptions @@ -60,7 +60,7 @@ public async Task Should_Allow_Subagents_To_Use_Sql_Tool_Via_Inherited_Sessionfs var session = await client.CreateSessionAsync(new SessionConfig { OnPermissionRequest = PermissionHandler.ApproveAll, - CreateSessionFsHandler = s => + CreateSessionFsProvider = s => { handler = new InMemorySessionFsSqliteHandler(s.SessionId, _sqliteCalls); return handler; @@ -68,7 +68,7 @@ public async Task Should_Allow_Subagents_To_Use_Sql_Tool_Via_Inherited_Sessionfs }); var events = new List(); - using var _ = session.On(evt => events.Add(evt)); + using var _ = session.On(evt => events.Add(evt)); await session.SendAndWaitAsync(new MessageOptions { diff --git a/dotnet/test/E2E/SessionLifecycleE2ETests.cs b/dotnet/test/E2E/SessionLifecycleE2ETests.cs index 19134cb53..31532def2 100644 --- a/dotnet/test/E2E/SessionLifecycleE2ETests.cs +++ b/dotnet/test/E2E/SessionLifecycleE2ETests.cs @@ -1,12 +1,12 @@ -/*--------------------------------------------------------------------------------------------- +/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ -using GitHub.Copilot.SDK.Test.Harness; +using GitHub.Copilot.Test.Harness; using Xunit; using Xunit.Abstractions; -namespace GitHub.Copilot.SDK.Test.E2E; +namespace GitHub.Copilot.Test.E2E; /// /// Lifecycle coverage at the level: listing @@ -84,7 +84,7 @@ await session.SendAndWaitAsync(new MessageOptions Prompt = "What is 2+2? Reply with just the number.", }); - var messages = await session.GetMessagesAsync(); + var messages = await session.GetEventsAsync(); Assert.NotEmpty(messages); // Should have at least session.start, user.message, assistant.message @@ -130,8 +130,8 @@ public async Task Should_Isolate_Events_Between_Concurrent_Sessions() var session1Events = new List(); var session2Events = new List(); - session1.On(evt => { lock (session1Events) { session1Events.Add(evt); } }); - session2.On(evt => { lock (session2Events) { session2Events.Add(evt); } }); + session1.On(evt => { lock (session1Events) { session1Events.Add(evt); } }); + session2.On(evt => { lock (session2Events) { session2Events.Add(evt); } }); // Send to both sessions await session1.SendAndWaitAsync(new MessageOptions diff --git a/dotnet/test/E2E/SessionMcpAndAgentConfigE2ETests.cs b/dotnet/test/E2E/SessionMcpAndAgentConfigE2ETests.cs index cc528b219..9567c98be 100644 --- a/dotnet/test/E2E/SessionMcpAndAgentConfigE2ETests.cs +++ b/dotnet/test/E2E/SessionMcpAndAgentConfigE2ETests.cs @@ -2,12 +2,12 @@ * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ -using GitHub.Copilot.SDK.Rpc; -using GitHub.Copilot.SDK.Test.Harness; +using GitHub.Copilot.Rpc; +using GitHub.Copilot.Test.Harness; using Xunit; using Xunit.Abstractions; -namespace GitHub.Copilot.SDK.Test.E2E; +namespace GitHub.Copilot.Test.E2E; public class SessionMcpAndAgentConfigE2ETests(E2ETestFixture fixture, ITestOutputHelper output) : E2ETestBase(fixture, "mcp_and_agents", output) { @@ -297,7 +297,7 @@ public async Task Should_Pass_Literal_Env_Values_To_Mcp_Server_Subprocess() Command = "node", Args = [Path.Combine(testHarnessDir, "test-mcp-server.mjs")], Env = new Dictionary { ["TEST_SECRET"] = "hunter2" }, - Cwd = testHarnessDir, + WorkingDirectory = testHarnessDir, Tools = ["*"] } }; @@ -358,7 +358,7 @@ await File.WriteAllTextAsync( "--config", configPath ], - Cwd = testHarnessDir, + WorkingDirectory = testHarnessDir, Tools = ["*"] } }; diff --git a/dotnet/test/E2E/SkillsE2ETests.cs b/dotnet/test/E2E/SkillsE2ETests.cs index 7da1d4b3e..76f84106f 100644 --- a/dotnet/test/E2E/SkillsE2ETests.cs +++ b/dotnet/test/E2E/SkillsE2ETests.cs @@ -5,7 +5,7 @@ using Xunit; using Xunit.Abstractions; -namespace GitHub.Copilot.SDK.Test.E2E; +namespace GitHub.Copilot.Test.E2E; public class SkillsE2ETests : E2ETestBase { diff --git a/dotnet/test/E2E/StreamingFidelityE2ETests.cs b/dotnet/test/E2E/StreamingFidelityE2ETests.cs index 82580a656..fec00f1cb 100644 --- a/dotnet/test/E2E/StreamingFidelityE2ETests.cs +++ b/dotnet/test/E2E/StreamingFidelityE2ETests.cs @@ -1,11 +1,11 @@ -/*--------------------------------------------------------------------------------------------- +/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ using Xunit; using Xunit.Abstractions; -namespace GitHub.Copilot.SDK.Test.E2E; +namespace GitHub.Copilot.Test.E2E; public class StreamingFidelityE2ETests(E2ETestFixture fixture, ITestOutputHelper output) : E2ETestBase(fixture, "streaming_fidelity", output) { @@ -15,7 +15,7 @@ public async Task Should_Produce_Delta_Events_When_Streaming_Is_Enabled() var session = await CreateSessionAsync(new SessionConfig { Streaming = true }); var events = new List(); - session.On(evt => { lock (events) { events.Add(evt); } }); + session.On(evt => { lock (events) { events.Add(evt); } }); await session.SendAndWaitAsync(new MessageOptions { Prompt = "Count from 1 to 5, separated by commas." }); @@ -51,7 +51,7 @@ public async Task Should_Not_Produce_Deltas_When_Streaming_Is_Disabled() var session = await CreateSessionAsync(new SessionConfig { Streaming = false }); var events = new List(); - session.On(evt => { lock (events) { events.Add(evt); } }); + session.On(evt => { lock (events) { events.Add(evt); } }); await session.SendAndWaitAsync(new MessageOptions { Prompt = "Say 'hello world'." }); @@ -83,7 +83,7 @@ public async Task Should_Produce_Deltas_After_Session_Resume() new ResumeSessionConfig { OnPermissionRequest = PermissionHandler.ApproveAll, Streaming = true }); var events = new List(); - session2.On(evt => { lock (events) { events.Add(evt); } }); + session2.On(evt => { lock (events) { events.Add(evt); } }); var answer = await session2.SendAndWaitAsync(new MessageOptions { Prompt = "Now if you double that, what do you get?" }); Assert.NotNull(answer); @@ -118,7 +118,7 @@ public async Task Should_Not_Produce_Deltas_After_Session_Resume_With_Streaming_ new ResumeSessionConfig { OnPermissionRequest = PermissionHandler.ApproveAll, Streaming = false }); var events = new List(); - session2.On(evt => { lock (events) { events.Add(evt); } }); + session2.On(evt => { lock (events) { events.Add(evt); } }); var answer = await session2.SendAndWaitAsync(new MessageOptions { Prompt = "Now if you double that, what do you get?" }); Assert.NotNull(answer); @@ -150,7 +150,7 @@ public async Task Should_Emit_Streaming_Deltas_With_Reasoning_Effort_Configured( }); var events = new List(); - session.On(evt => { lock (events) { events.Add(evt); } }); + session.On(evt => { lock (events) { events.Add(evt); } }); await session.SendAndWaitAsync(new MessageOptions { Prompt = "What is 15 * 17?" }); @@ -167,7 +167,7 @@ public async Task Should_Emit_Streaming_Deltas_With_Reasoning_Effort_Configured( Assert.Contains("255", assistantEvents.Last().Data.Content ?? string.Empty); // Verify the session was created with reasoning effort via GetMessages - var messages = await session.GetMessagesAsync(); + var messages = await session.GetEventsAsync(); var startEvent = Assert.Single(messages.OfType()); Assert.Equal("high", startEvent.Data.ReasoningEffort); @@ -180,7 +180,7 @@ public async Task Should_Emit_AssistantMessageStart_Before_Deltas_With_Matching_ var session = await CreateSessionAsync(new SessionConfig { Streaming = true }); var events = new List(); - session.On(evt => { lock (events) { events.Add(evt); } }); + session.On(evt => { lock (events) { events.Add(evt); } }); await session.SendAndWaitAsync(new MessageOptions { Prompt = "Count from 1 to 5, separated by commas." }); diff --git a/dotnet/test/E2E/SubagentHooksE2ETests.cs b/dotnet/test/E2E/SubagentHooksE2ETests.cs index 1a9c8ffa1..5c8543215 100644 --- a/dotnet/test/E2E/SubagentHooksE2ETests.cs +++ b/dotnet/test/E2E/SubagentHooksE2ETests.cs @@ -3,11 +3,11 @@ *--------------------------------------------------------------------------------------------*/ using System.Collections.Concurrent; -using GitHub.Copilot.SDK.Test.Harness; +using GitHub.Copilot.Test.Harness; using Xunit; using Xunit.Abstractions; -namespace GitHub.Copilot.SDK.Test.E2E; +namespace GitHub.Copilot.Test.E2E; public class SubagentHooksE2ETests(E2ETestFixture fixture, ITestOutputHelper output) : E2ETestBase(fixture, "subagent_hooks", output) diff --git a/dotnet/test/E2E/SuspendE2ETests.cs b/dotnet/test/E2E/SuspendE2ETests.cs index 44dcef7dd..d3aa8067d 100644 --- a/dotnet/test/E2E/SuspendE2ETests.cs +++ b/dotnet/test/E2E/SuspendE2ETests.cs @@ -7,7 +7,7 @@ using Xunit; using Xunit.Abstractions; -namespace GitHub.Copilot.SDK.Test.E2E; +namespace GitHub.Copilot.Test.E2E; /// /// E2E coverage for the session.suspend RPC. Suspend is a graceful shutdown @@ -50,12 +50,12 @@ public async Task Should_Suspend_Idle_Session_Without_Throwing() public async Task Should_Allow_Resume_And_Continue_Conversation_After_Suspend() { const string sharedToken = "suspend-shared-token"; - await using var server = Ctx.CreateClient(useStdio: false, options: new CopilotClientOptions { TcpConnectionToken = sharedToken }); + await using var server = Ctx.CreateClient(options: new CopilotClientOptions { Connection = RuntimeConnection.ForTcp(connectionToken: sharedToken) }); await server.StartAsync(); var cliUrl = GetCliUrl(server); string sessionId; - await using (var client1 = Ctx.CreateClient(options: new CopilotClientOptions { CliUrl = cliUrl, TcpConnectionToken = sharedToken })) + await using (var client1 = Ctx.CreateClient(options: new CopilotClientOptions { Connection = RuntimeConnection.ForUri(cliUrl, connectionToken: sharedToken) })) { var session1 = await client1.CreateSessionAsync(new SessionConfig { @@ -76,7 +76,7 @@ await session1.SendAndWaitAsync(new MessageOptions // A different client should be able to pick the session back up. The previous // turn was completed before suspend, so there is no pending work to continue. - await using var client2 = Ctx.CreateClient(options: new CopilotClientOptions { CliUrl = cliUrl, TcpConnectionToken = sharedToken }); + await using var client2 = Ctx.CreateClient(options: new CopilotClientOptions { Connection = RuntimeConnection.ForUri(cliUrl, connectionToken: sharedToken) }); var session2 = await client2.ResumeSessionAsync(sessionId, new ResumeSessionConfig { OnPermissionRequest = PermissionHandler.ApproveAll, @@ -175,7 +175,7 @@ public async Task Should_Reject_Pending_External_Tool_When_Suspending() OnPermissionRequest = PermissionHandler.ApproveAll, }); - using var subscription = session.On(evt => + using var subscription = session.On(evt => { if (evt is ExternalToolRequestedEvent ext && ext.Data.ToolName == "suspend_reject_external_tool") { @@ -219,7 +219,7 @@ async Task BlockingTool([Description("Value to look up")] string value) private static string GetCliUrl(CopilotClient client) { - var port = client.ActualPort + var port = client.RuntimePort ?? throw new InvalidOperationException("Expected the test server to be listening on a TCP port."); return $"localhost:{port}"; } diff --git a/dotnet/test/E2E/SystemMessageTransformE2ETests.cs b/dotnet/test/E2E/SystemMessageTransformE2ETests.cs index 5af704834..91f942190 100644 --- a/dotnet/test/E2E/SystemMessageTransformE2ETests.cs +++ b/dotnet/test/E2E/SystemMessageTransformE2ETests.cs @@ -2,11 +2,11 @@ * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ -using GitHub.Copilot.SDK.Test.Harness; +using GitHub.Copilot.Test.Harness; using Xunit; using Xunit.Abstractions; -namespace GitHub.Copilot.SDK.Test.E2E; +namespace GitHub.Copilot.Test.E2E; public class SystemMessageTransformE2ETests(E2ETestFixture fixture, ITestOutputHelper output) : E2ETestBase(fixture, "system_message_transform", output) { diff --git a/dotnet/test/E2E/TelemetryExportE2ETests.cs b/dotnet/test/E2E/TelemetryExportE2ETests.cs index b4fced4e2..ceec2326e 100644 --- a/dotnet/test/E2E/TelemetryExportE2ETests.cs +++ b/dotnet/test/E2E/TelemetryExportE2ETests.cs @@ -2,13 +2,13 @@ * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ -using GitHub.Copilot.SDK.Test.Harness; +using GitHub.Copilot.Test.Harness; using Microsoft.Extensions.AI; using System.Text.Json; using Xunit; using Xunit.Abstractions; -namespace GitHub.Copilot.SDK.Test.E2E; +namespace GitHub.Copilot.Test.E2E; public class TelemetryExportE2ETests(E2ETestFixture fixture, ITestOutputHelper output) : E2ETestBase(fixture, "telemetry", output) diff --git a/dotnet/test/E2E/ToolResultsE2ETests.cs b/dotnet/test/E2E/ToolResultsE2ETests.cs index c1283baa5..75fd9488e 100644 --- a/dotnet/test/E2E/ToolResultsE2ETests.cs +++ b/dotnet/test/E2E/ToolResultsE2ETests.cs @@ -2,7 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ -using GitHub.Copilot.SDK.Test.Harness; +using GitHub.Copilot.Test.Harness; using Microsoft.Extensions.AI; using System.ComponentModel; using System.Text.Json; @@ -10,7 +10,7 @@ using Xunit; using Xunit.Abstractions; -namespace GitHub.Copilot.SDK.Test.E2E; +namespace GitHub.Copilot.Test.E2E; public partial class ToolResultsE2ETests(E2ETestFixture fixture, ITestOutputHelper output) : E2ETestBase(fixture, "tool_results", output) { @@ -131,7 +131,7 @@ public async Task Should_Handle_Tool_Result_With_Rejected_ResultType() OnPermissionRequest = PermissionHandler.ApproveAll, }); - session.On(evt => + session.On(evt => { if (evt is ToolExecutionCompleteEvent toolEvt) { @@ -181,7 +181,7 @@ public async Task Should_Handle_Tool_Result_With_Denied_ResultType() OnPermissionRequest = PermissionHandler.ApproveAll, }); - session.On(evt => + session.On(evt => { if (evt is ToolExecutionCompleteEvent toolEvt) { diff --git a/dotnet/test/E2E/ToolsE2ETests.cs b/dotnet/test/E2E/ToolsE2ETests.cs index 529223894..c36bf2294 100644 --- a/dotnet/test/E2E/ToolsE2ETests.cs +++ b/dotnet/test/E2E/ToolsE2ETests.cs @@ -2,7 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ -using GitHub.Copilot.SDK.Test.Harness; +using GitHub.Copilot.Test.Harness; using Microsoft.Extensions.AI; using System.Collections.ObjectModel; using System.ComponentModel; @@ -11,7 +11,7 @@ using Xunit; using Xunit.Abstractions; -namespace GitHub.Copilot.SDK.Test.E2E; +namespace GitHub.Copilot.Test.E2E; public partial class ToolsE2ETests(E2ETestFixture fixture, ITestOutputHelper output) : E2ETestBase(fixture, "tools", output) { @@ -240,7 +240,7 @@ await session.SendAsync(new MessageOptions BinaryResultsForLlm = [new() { // 2x2 yellow square Data = "iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAADklEQVR4nGP4/5/h/38GABkAA/0k+7UAAAAASUVORK5CYII=", - Type = "base64", + Type = ToolBinaryResultType.Image, MimeType = "image/png", }], SessionLog = "Returned an image", diff --git a/dotnet/test/GitHub.Copilot.SDK.Test.csproj b/dotnet/test/GitHub.Copilot.SDK.Test.csproj index cdff9b014..57f89bcd9 100644 --- a/dotnet/test/GitHub.Copilot.SDK.Test.csproj +++ b/dotnet/test/GitHub.Copilot.SDK.Test.csproj @@ -3,6 +3,7 @@ net8.0 net8.0;net472 + GitHub.Copilot.Test false true $(NoWarn);GHCP001 diff --git a/dotnet/test/Harness/CapiProxy.cs b/dotnet/test/Harness/CapiProxy.cs index 274055540..9b80651bc 100644 --- a/dotnet/test/Harness/CapiProxy.cs +++ b/dotnet/test/Harness/CapiProxy.cs @@ -10,7 +10,7 @@ using System.Text.Json.Serialization; using System.Text.RegularExpressions; -namespace GitHub.Copilot.SDK.Test.Harness; +namespace GitHub.Copilot.Test.Harness; public sealed partial class CapiProxy : IAsyncDisposable { diff --git a/dotnet/test/Harness/E2ETestBase.cs b/dotnet/test/Harness/E2ETestBase.cs index ca5d2b816..592253bda 100644 --- a/dotnet/test/Harness/E2ETestBase.cs +++ b/dotnet/test/Harness/E2ETestBase.cs @@ -2,14 +2,14 @@ * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ -using GitHub.Copilot.SDK.Test.Harness; +using GitHub.Copilot.Test.Harness; using Microsoft.Extensions.Logging; using System.Data; using System.Reflection; using Xunit; using Xunit.Abstractions; -namespace GitHub.Copilot.SDK.Test; +namespace GitHub.Copilot.Test; public abstract class E2ETestBase : IClassFixture, IAsyncLifetime { @@ -88,13 +88,12 @@ protected async Task ResumeSessionAsync(string sessionId, Resume config.OnPermissionRequest ??= PermissionHandler.ApproveAll; await Client.StartAsync(); - var port = Client.ActualPort + var port = Client.RuntimePort ?? throw new InvalidOperationException("The shared E2E client must use TCP transport to support multi-client resume."); var client = Ctx.CreateClient(options: new CopilotClientOptions { - CliUrl = $"localhost:{port}", - TcpConnectionToken = E2ETestFixture.SharedTcpConnectionToken, + Connection = RuntimeConnection.ForUri($"localhost:{port}", connectionToken: E2ETestFixture.SharedTcpConnectionToken), }); return await client.ResumeSessionAsync(sessionId, config); } diff --git a/dotnet/test/Harness/E2ETestContext.cs b/dotnet/test/Harness/E2ETestContext.cs index ef703c4be..1819eb634 100644 --- a/dotnet/test/Harness/E2ETestContext.cs +++ b/dotnet/test/Harness/E2ETestContext.cs @@ -3,10 +3,11 @@ *--------------------------------------------------------------------------------------------*/ using Microsoft.Extensions.Logging; +using System.Diagnostics; using System.Runtime.CompilerServices; using System.Text.RegularExpressions; -namespace GitHub.Copilot.SDK.Test.Harness; +namespace GitHub.Copilot.Test.Harness; public sealed class E2ETestContext : IAsyncDisposable { @@ -195,15 +196,19 @@ public IReadOnlyDictionary GetEnvironment() env["GH_ENTERPRISE_TOKEN"] = ""; env["GITHUB_ENTERPRISE_TOKEN"] = ""; } - if (Environment.GetEnvironmentVariable("GITHUB_ACTIONS") == "true") - { - env["GH_TOKEN"] = DefaultGitHubToken; - env["GITHUB_TOKEN"] = DefaultGitHubToken; - } + + env["GITHUB_TOKEN"] = env["GH_TOKEN"] = DefaultGitHubToken; return env!; } + private static string? GetEffectiveGitHubTokenForTests() + { + return Environment.GetEnvironmentVariable("GITHUB_ACTIONS") == "true" + ? DefaultGitHubToken + : Environment.GetEnvironmentVariable("GITHUB_TOKEN"); + } + public CopilotClient CreateClient( bool? useStdio = null, CopilotClientOptions? options = null, @@ -212,21 +217,41 @@ public CopilotClient CreateClient( { options ??= new CopilotClientOptions(); - options.Cwd ??= WorkDir; + options.WorkingDirectory ??= WorkDir; options.Environment ??= GetEnvironment(); - options.UseStdio = useStdio; options.Logger ??= Logger; - if (string.IsNullOrEmpty(options.CliUrl)) + // Build the connection. If the caller supplied one, just ensure the runtime path is set; + // otherwise default to Stdio with the bundled runtime (matches CopilotClient's own default). + // useStdio is a convenience shortcut for the no-Connection case; passing both is ambiguous. + if (useStdio is not null && options.Connection is not null) + { + throw new ArgumentException( + "Specify either useStdio or options.Connection, not both. " + + "Use options.Connection (e.g. RuntimeConnection.ForStdio() / RuntimeConnection.ForTcp()) to control transport when supplying a Connection.", + nameof(useStdio)); + } + + var cliPath = GetCliPath(_repoRoot); + switch (options.Connection) { - options.CliPath ??= GetCliPath(_repoRoot); + case null: + options.Connection = useStdio == false + ? RuntimeConnection.ForTcp(path: cliPath) + : RuntimeConnection.ForStdio(path: cliPath); + break; + case ChildProcessRuntimeConnection child when child.Path is null: + child.Path = cliPath; + break; } + // Auto-inject auth token unless connecting to an existing runtime via URI. + var isExistingRuntime = options.Connection is UriRuntimeConnection; if (autoInjectGitHubToken && string.IsNullOrEmpty(options.GitHubToken) - && string.IsNullOrEmpty(options.CliUrl)) + && !isExistingRuntime) { - options.GitHubToken = DefaultGitHubToken; + options.GitHubToken = GetEffectiveGitHubTokenForTests(); } var client = new CopilotClient(options); diff --git a/dotnet/test/Harness/E2ETestFixture.cs b/dotnet/test/Harness/E2ETestFixture.cs index a53e4bd32..95bebc139 100644 --- a/dotnet/test/Harness/E2ETestFixture.cs +++ b/dotnet/test/Harness/E2ETestFixture.cs @@ -2,10 +2,10 @@ * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ -using GitHub.Copilot.SDK.Test.Harness; +using GitHub.Copilot.Test.Harness; using Xunit; -namespace GitHub.Copilot.SDK.Test; +namespace GitHub.Copilot.Test; public class E2ETestFixture : IAsyncLifetime { @@ -17,9 +17,9 @@ public class E2ETestFixture : IAsyncLifetime public async Task InitializeAsync() { Ctx = await E2ETestContext.CreateAsync(); - Client = Ctx.CreateClient(useStdio: false, options: new CopilotClientOptions + Client = Ctx.CreateClient(options: new CopilotClientOptions { - TcpConnectionToken = SharedTcpConnectionToken, + Connection = RuntimeConnection.ForTcp(connectionToken: SharedTcpConnectionToken), }, persistent: true); } diff --git a/dotnet/test/Harness/TestHelper.cs b/dotnet/test/Harness/TestHelper.cs index 1afd21d3c..5d5839618 100644 --- a/dotnet/test/Harness/TestHelper.cs +++ b/dotnet/test/Harness/TestHelper.cs @@ -1,8 +1,8 @@ -/*--------------------------------------------------------------------------------------------- +/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ -namespace GitHub.Copilot.SDK.Test.Harness; +namespace GitHub.Copilot.Test.Harness; public static class TestHelper { @@ -40,7 +40,7 @@ void TryComplete() if (snapshot != null && idle) tcs.TrySetResult(snapshot); } - using var subscription = session.On(evt => + using var subscription = session.On(evt => { switch (evt) { @@ -91,7 +91,7 @@ async void CheckExistingMessages() private static async Task<(AssistantMessageEvent? Final, bool SawIdle)> GetExistingMessagesAsync(CopilotSession session, bool alreadyIdle) { - var messages = (await session.GetMessagesAsync()).ToList(); + var messages = (await session.GetEventsAsync()).ToList(); var lastUserIdx = messages.FindLastIndex(m => m is UserMessageEvent); var currentTurn = lastUserIdx < 0 ? messages : messages.Skip(lastUserIdx).ToList(); @@ -127,7 +127,7 @@ public static async Task GetNextEventOfTypeAsync( var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); using var cts = new CancellationTokenSource(timeout ?? DefaultEventTimeout); - using var subscription = session.On(evt => + using var subscription = session.On(evt => { if (evt is T matched && predicate(matched)) { diff --git a/dotnet/test/Unit/ClientSessionLifetimeTests.cs b/dotnet/test/Unit/ClientSessionLifetimeTests.cs index cf59b0fbb..5bd3335a5 100644 --- a/dotnet/test/Unit/ClientSessionLifetimeTests.cs +++ b/dotnet/test/Unit/ClientSessionLifetimeTests.cs @@ -11,7 +11,7 @@ using System.Text.Json; using Xunit; -namespace GitHub.Copilot.SDK.Test.Unit; +namespace GitHub.Copilot.Test.Unit; public sealed class ClientSessionLifetimeTests { @@ -19,7 +19,7 @@ public sealed class ClientSessionLifetimeTests public async Task Dropped_Session_Remains_Rooted_By_Client() { await using var server = await FakeCopilotServer.StartAsync(); - await using var client = new CopilotClient(new CopilotClientOptions { CliUrl = server.Url }); + await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) }); var weakSession = await CreateDroppedSessionAsync(client); @@ -36,7 +36,7 @@ public async Task Dropped_Session_Remains_Rooted_By_Client() public async Task Disposed_Session_Is_Removed_From_Client() { await using var server = await FakeCopilotServer.StartAsync(); - await using var client = new CopilotClient(new CopilotClientOptions { CliUrl = server.Url }); + await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) }); var session = await client.CreateSessionAsync(new SessionConfig { @@ -54,7 +54,7 @@ public async Task Disposing_Session_Remains_Rooted_Until_Destroy_Completes() { await using var server = await FakeCopilotServer.StartAsync(); server.DelayDestroy(); - await using var client = new CopilotClient(new CopilotClientOptions { CliUrl = server.Url }); + await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) }); var session = await client.CreateSessionAsync(new SessionConfig { @@ -77,7 +77,7 @@ public async Task Disposing_Session_Remains_Rooted_Until_Destroy_Completes() public async Task StopAsync_Removes_Rooted_Sessions() { await using var server = await FakeCopilotServer.StartAsync(); - await using var client = new CopilotClient(new CopilotClientOptions { CliUrl = server.Url }); + await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) }); _ = await client.CreateSessionAsync(new SessionConfig { @@ -95,7 +95,7 @@ public async Task StopAsync_Keeps_Session_Rooted_Until_Destroy_Completes() { await using var server = await FakeCopilotServer.StartAsync(); server.DelayDestroy(); - await using var client = new CopilotClient(new CopilotClientOptions { CliUrl = server.Url }); + await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) }); _ = await client.CreateSessionAsync(new SessionConfig { @@ -118,7 +118,7 @@ public async Task StopAsync_Keeps_Session_Rooted_Until_Destroy_Completes() public async Task ResumeSessionAsync_Throws_When_Same_Client_Already_Tracks_Session() { await using var server = await FakeCopilotServer.StartAsync(); - await using var client = new CopilotClient(new CopilotClientOptions { CliUrl = server.Url }); + await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) }); var sessionId = "same-session-id"; await using var session = await client.CreateSessionAsync(new SessionConfig @@ -140,7 +140,7 @@ public async Task ResumeSessionAsync_Throws_When_Same_Client_Already_Tracks_Sess public async Task Generated_Session_Rpc_Throws_When_Session_Disposed() { await using var server = await FakeCopilotServer.StartAsync(); - await using var client = new CopilotClient(new CopilotClientOptions { CliUrl = server.Url }); + await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) }); var session = await client.CreateSessionAsync(new SessionConfig { diff --git a/dotnet/test/Unit/CloneTests.cs b/dotnet/test/Unit/CloneTests.cs index 0816da9b2..184c13b18 100644 --- a/dotnet/test/Unit/CloneTests.cs +++ b/dotnet/test/Unit/CloneTests.cs @@ -1,10 +1,10 @@ -/*--------------------------------------------------------------------------------------------- +/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ using Xunit; -namespace GitHub.Copilot.SDK.Test.Unit; +namespace GitHub.Copilot.Test.Unit; public class CloneTests { @@ -13,57 +13,39 @@ public void CopilotClientOptions_Clone_CopiesAllProperties() { var original = new CopilotClientOptions { - CliPath = "/usr/bin/copilot", - CliArgs = ["--verbose", "--debug"], - Cwd = "/home/user", - Port = 8080, - UseStdio = false, - CliUrl = "http://localhost:8080", - LogLevel = "debug", - AutoStart = false, - + Connection = RuntimeConnection.ForTcp(port: 8080, connectionToken: "tok", path: "/usr/bin/copilot", args: ["--verbose", "--debug"]), + WorkingDirectory = "/home/user", + LogLevel = CopilotLogLevel.Debug, Environment = new Dictionary { ["KEY"] = "value" }, GitHubToken = "ghp_test", UseLoggedInUser = false, - CopilotHome = "/custom/copilot/home", - Remote = true, + BaseDirectory = "/custom/copilot/home", + EnableRemoteSessions = true, SessionIdleTimeoutSeconds = 600, }; var clone = original.Clone(); - Assert.Equal(original.CliPath, clone.CliPath); - Assert.Equal(original.CliArgs, clone.CliArgs); - Assert.Equal(original.Cwd, clone.Cwd); - Assert.Equal(original.Port, clone.Port); - Assert.Equal(original.UseStdio, clone.UseStdio); - Assert.Equal(original.CliUrl, clone.CliUrl); + Assert.Same(original.Connection, clone.Connection); + Assert.Equal(original.WorkingDirectory, clone.WorkingDirectory); Assert.Equal(original.LogLevel, clone.LogLevel); - Assert.Equal(original.AutoStart, clone.AutoStart); - Assert.Equal(original.Environment, clone.Environment); Assert.Equal(original.GitHubToken, clone.GitHubToken); Assert.Equal(original.UseLoggedInUser, clone.UseLoggedInUser); - Assert.Equal(original.CopilotHome, clone.CopilotHome); - Assert.Equal(original.Remote, clone.Remote); + Assert.Equal(original.BaseDirectory, clone.BaseDirectory); + Assert.Equal(original.EnableRemoteSessions, clone.EnableRemoteSessions); Assert.Equal(original.SessionIdleTimeoutSeconds, clone.SessionIdleTimeoutSeconds); } [Fact] - public void CopilotClientOptions_Clone_CollectionsAreIndependent() + public void CopilotClientOptions_Clone_ConnectionIsShared() { - var original = new CopilotClientOptions - { - CliArgs = ["--verbose"], - }; + var connection = RuntimeConnection.ForStdio(); + var original = new CopilotClientOptions { Connection = connection }; var clone = original.Clone(); - // Mutate clone array - clone.CliArgs![0] = "--quiet"; - - // Original is unaffected - Assert.Equal("--verbose", original.CliArgs![0]); + Assert.Same(connection, clone.Connection); } [Fact] @@ -109,8 +91,8 @@ public void SessionConfig_Clone_CopiesAllProperties() SkillDirectories = ["/skills"], InstructionDirectories = ["/instructions"], DisabledSkills = ["skill1"], - OnExitPlanMode = static (_, _) => Task.FromResult(new ExitPlanModeResult()), - OnAutoModeSwitch = static (_, _) => Task.FromResult(AutoModeSwitchResponse.No), + OnExitPlanModeRequest = static (_, _) => Task.FromResult(new ExitPlanModeResult()), + OnAutoModeSwitchRequest = static (_, _) => Task.FromResult(AutoModeSwitchResponse.No), }; var clone = original.Clone(); @@ -135,8 +117,8 @@ public void SessionConfig_Clone_CopiesAllProperties() Assert.Equal(original.SkillDirectories, clone.SkillDirectories); Assert.Equal(original.InstructionDirectories, clone.InstructionDirectories); Assert.Equal(original.DisabledSkills, clone.DisabledSkills); - Assert.Same(original.OnExitPlanMode, clone.OnExitPlanMode); - Assert.Same(original.OnAutoModeSwitch, clone.OnAutoModeSwitch); + Assert.Same(original.OnExitPlanModeRequest, clone.OnExitPlanModeRequest); + Assert.Same(original.OnAutoModeSwitchRequest, clone.OnAutoModeSwitchRequest); } [Fact] @@ -315,14 +297,14 @@ public void ResumeSessionConfig_Clone_CopiesModeSwitchHandlers() { var original = new ResumeSessionConfig { - OnExitPlanMode = static (_, _) => Task.FromResult(new ExitPlanModeResult()), - OnAutoModeSwitch = static (_, _) => Task.FromResult(AutoModeSwitchResponse.No), + OnExitPlanModeRequest = static (_, _) => Task.FromResult(new ExitPlanModeResult()), + OnAutoModeSwitchRequest = static (_, _) => Task.FromResult(AutoModeSwitchResponse.No), }; var clone = original.Clone(); - Assert.Same(original.OnExitPlanMode, clone.OnExitPlanMode); - Assert.Same(original.OnAutoModeSwitch, clone.OnAutoModeSwitch); + Assert.Same(original.OnExitPlanModeRequest, clone.OnExitPlanModeRequest); + Assert.Same(original.OnAutoModeSwitchRequest, clone.OnAutoModeSwitchRequest); } [Fact] diff --git a/dotnet/test/Unit/CopilotToolTests.cs b/dotnet/test/Unit/CopilotToolTests.cs index e1cb228fe..76ad0e425 100644 --- a/dotnet/test/Unit/CopilotToolTests.cs +++ b/dotnet/test/Unit/CopilotToolTests.cs @@ -7,7 +7,7 @@ using System.Text.Json; using Xunit; -namespace GitHub.Copilot.SDK.Test.Unit; +namespace GitHub.Copilot.Test.Unit; public class CopilotToolTests { diff --git a/dotnet/test/Unit/ForwardCompatibilityTests.cs b/dotnet/test/Unit/ForwardCompatibilityTests.cs index d0265c361..09133dfb5 100644 --- a/dotnet/test/Unit/ForwardCompatibilityTests.cs +++ b/dotnet/test/Unit/ForwardCompatibilityTests.cs @@ -6,7 +6,7 @@ using System.Text.Json.Serialization; using Xunit; -namespace GitHub.Copilot.SDK.Test.Unit; +namespace GitHub.Copilot.Test.Unit; /// /// Tests for forward-compatible handling of unknown session event types. @@ -240,7 +240,7 @@ public void RpcEnum_WithNonStringValue_ThrowsJsonException() [Fact] public void RpcEnum_DefaultValue_HasEmptyStringValue() { - GitHub.Copilot.SDK.SessionMode mode = default; + GitHub.Copilot.SessionMode mode = default; Assert.Equal(string.Empty, mode.Value); Assert.Equal(string.Empty, mode.ToString()); @@ -249,7 +249,7 @@ public void RpcEnum_DefaultValue_HasEmptyStringValue() [Fact] public void RpcEnum_DefaultValueSerialization_ThrowsJsonException() { - GitHub.Copilot.SDK.SessionMode mode = default; + GitHub.Copilot.SessionMode mode = default; var exception = Assert.Throws(() => JsonSerializer.Serialize( mode, @@ -304,5 +304,5 @@ public void FromJson_UnknownEventType_PreservesAgentIdNull() } } -[JsonSerializable(typeof(GitHub.Copilot.SDK.SessionMode))] +[JsonSerializable(typeof(GitHub.Copilot.SessionMode))] internal partial class ForwardCompatibilityJsonContext : JsonSerializerContext; diff --git a/dotnet/test/Unit/JsonRpcTests.cs b/dotnet/test/Unit/JsonRpcTests.cs index f4101956f..6c045c8eb 100644 --- a/dotnet/test/Unit/JsonRpcTests.cs +++ b/dotnet/test/Unit/JsonRpcTests.cs @@ -7,12 +7,12 @@ using System.Text.Json.Serialization.Metadata; using Xunit; -namespace GitHub.Copilot.SDK.Test.Unit; +namespace GitHub.Copilot.Test.Unit; /// /// Behavior tests for the SDK's hand-rolled JSON-RPC transport (params shape, serializer /// metadata, request/response routing, error propagation). Reflection is used to force -/// every generated JsonSerializable registration on the , +/// every generated JsonSerializable registration on the , /// which guards against regressions in the C# code generator (scripts/codegen/csharp.ts) /// silently dropping a registration. Functional behavior of individual RPC methods lives /// in the Rpc*Tests classes; this file owns transport- and serializer-shape concerns. @@ -160,7 +160,7 @@ public void Dispose() private sealed class JsonRpcReflection : IDisposable { private static readonly Type JsonRpcType = - typeof(CopilotClient).Assembly.GetType("GitHub.Copilot.SDK.JsonRpc", throwOnError: true)!; + typeof(CopilotClient).Assembly.GetType("GitHub.Copilot.JsonRpc", throwOnError: true)!; private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web) { diff --git a/dotnet/test/Unit/MSBuildTargetsTests.cs b/dotnet/test/Unit/MSBuildTargetsTests.cs index 745069ca4..a7d9cc025 100644 --- a/dotnet/test/Unit/MSBuildTargetsTests.cs +++ b/dotnet/test/Unit/MSBuildTargetsTests.cs @@ -7,7 +7,7 @@ using System.Text; using Xunit; -namespace GitHub.Copilot.SDK.Test.Unit; +namespace GitHub.Copilot.Test.Unit; /// /// Integration tests for the MSBuild targets shipped in diff --git a/dotnet/test/Unit/PermissionRequestResultKindTests.cs b/dotnet/test/Unit/PermissionRequestResultKindTests.cs index 67c9eeb41..ce828e6ef 100644 --- a/dotnet/test/Unit/PermissionRequestResultKindTests.cs +++ b/dotnet/test/Unit/PermissionRequestResultKindTests.cs @@ -5,7 +5,7 @@ using System.Text.Json; using Xunit; -namespace GitHub.Copilot.SDK.Test.Unit; +namespace GitHub.Copilot.Test.Unit; public class PermissionRequestResultKindTests { @@ -21,13 +21,6 @@ public void WellKnownKinds_HaveExpectedValues() Assert.Equal("reject", PermissionRequestResultKind.Rejected.Value); Assert.Equal("user-not-available", PermissionRequestResultKind.UserNotAvailable.Value); Assert.Equal("no-result", PermissionRequestResultKind.NoResult.Value); - - // Deprecated aliases still resolve -#pragma warning disable CS0618 - Assert.Equal(PermissionRequestResultKind.Rejected, PermissionRequestResultKind.DeniedInteractivelyByUser); - Assert.Equal(PermissionRequestResultKind.UserNotAvailable, PermissionRequestResultKind.DeniedCouldNotRequestFromUser); - Assert.Equal(PermissionRequestResultKind.UserNotAvailable, PermissionRequestResultKind.DeniedByRules); -#pragma warning restore CS0618 } [Fact] diff --git a/dotnet/test/Unit/PublicDtoTests.cs b/dotnet/test/Unit/PublicDtoTests.cs index 76a0d80bf..473c312ba 100644 --- a/dotnet/test/Unit/PublicDtoTests.cs +++ b/dotnet/test/Unit/PublicDtoTests.cs @@ -7,7 +7,7 @@ using System.Text.Json; using Xunit; -namespace GitHub.Copilot.SDK.Test.Unit; +namespace GitHub.Copilot.Test.Unit; /// /// Reflection-based safety net that exercises the get/set surface of every public DTO in @@ -29,7 +29,7 @@ public void Public_Dto_Properties_Can_Be_Set_And_Read() .GetTypes() .Where(type => type is { IsClass: true, IsAbstract: false, IsPublic: true } && - type.Namespace?.StartsWith("GitHub.Copilot.SDK", StringComparison.Ordinal) == true && + type.Namespace?.StartsWith("GitHub.Copilot", StringComparison.Ordinal) == true && type.GetConstructor(Type.EmptyTypes) is not null) .OrderBy(type => type.FullName, StringComparer.Ordinal); diff --git a/dotnet/test/Unit/SerializationTests.cs b/dotnet/test/Unit/SerializationTests.cs index 1ca6562f8..c361987dd 100644 --- a/dotnet/test/Unit/SerializationTests.cs +++ b/dotnet/test/Unit/SerializationTests.cs @@ -1,4 +1,4 @@ -/*--------------------------------------------------------------------------------------------- +/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ @@ -7,9 +7,9 @@ #if !NET8_0_OR_GREATER using System.Runtime.Serialization; #endif -using GitHub.Copilot.SDK.Rpc; +using GitHub.Copilot.Rpc; -namespace GitHub.Copilot.SDK.Test.Unit; +namespace GitHub.Copilot.Test.Unit; /// /// Tests for JSON serialization compatibility with the SDK's configured options. @@ -26,7 +26,7 @@ public void ProviderConfig_CanSerializeHeaders_WithSdkOptions() Headers = new Dictionary { ["Authorization"] = "Bearer provider-token" }, ModelId = "gpt-4o", WireModel = "my-finetune-v3", - MaxInputTokens = 100_000, + MaxPromptTokens = 100_000, MaxOutputTokens = 4096 }; @@ -46,7 +46,7 @@ public void ProviderConfig_CanSerializeHeaders_WithSdkOptions() Assert.Equal("Bearer provider-token", deserialized.Headers!["Authorization"]); Assert.Equal("gpt-4o", deserialized.ModelId); Assert.Equal("my-finetune-v3", deserialized.WireModel); - Assert.Equal(100_000, deserialized.MaxInputTokens); + Assert.Equal(100_000, deserialized.MaxPromptTokens); Assert.Equal(4096, deserialized.MaxOutputTokens); } @@ -265,7 +265,7 @@ public void McpHttpServerConfig_CanSerializeOauthOptions_WithSdkOptions() Assert.Equal("client-id", httpConfig.OauthClientId); Assert.False(httpConfig.OauthPublicClient); Assert.Equal(McpHttpServerConfigOauthGrantType.ClientCredentials, httpConfig.OauthGrantType); - Assert.Equal("*", Assert.Single(httpConfig.Tools)); + Assert.Equal("*", Assert.Single(httpConfig.Tools!)); Assert.Equal(3000, httpConfig.Timeout); } diff --git a/dotnet/test/Unit/SessionEventSerializationTests.cs b/dotnet/test/Unit/SessionEventSerializationTests.cs index 3622821dc..3e6d4661f 100644 --- a/dotnet/test/Unit/SessionEventSerializationTests.cs +++ b/dotnet/test/Unit/SessionEventSerializationTests.cs @@ -5,7 +5,7 @@ using System.Text.Json; using Xunit; -namespace GitHub.Copilot.SDK.Test.Unit; +namespace GitHub.Copilot.Test.Unit; public class SessionEventSerializationTests { diff --git a/dotnet/test/Unit/TelemetryTests.cs b/dotnet/test/Unit/TelemetryTests.cs index 1a2fdc6e5..9229285b9 100644 --- a/dotnet/test/Unit/TelemetryTests.cs +++ b/dotnet/test/Unit/TelemetryTests.cs @@ -6,7 +6,7 @@ using System.Reflection; using Xunit; -namespace GitHub.Copilot.SDK.Test.Unit; +namespace GitHub.Copilot.Test.Unit; public class TelemetryTests { @@ -91,7 +91,7 @@ public void TelemetryHelpers_Restores_W3C_Trace_Context() private static T InvokeTelemetryHelper(string name, params object?[] args) { - var helperType = typeof(CopilotClient).Assembly.GetType("GitHub.Copilot.SDK.TelemetryHelpers", throwOnError: true)!; + var helperType = typeof(CopilotClient).Assembly.GetType("GitHub.Copilot.TelemetryHelpers", throwOnError: true)!; var method = helperType.GetMethod(name, BindingFlags.Static | BindingFlags.NonPublic)!; return (T)method.Invoke(null, args)!; } diff --git a/scripts/codegen/csharp.ts b/scripts/codegen/csharp.ts index 8fd6b6f58..f35c0a52b 100644 --- a/scripts/codegen/csharp.ts +++ b/scripts/codegen/csharp.ts @@ -569,12 +569,12 @@ function getOrCreateEnum( lines.push(` /// `); lines.push(` public override ${enumName} Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)`); lines.push(` {`); - lines.push(` return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert));`); + lines.push(` return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert));`); lines.push(` }`, ""); lines.push(` /// `); lines.push(` public override void Write(Utf8JsonWriter writer, ${enumName} value, JsonSerializerOptions options)`); lines.push(` {`); - lines.push(` GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(${enumName}));`); + lines.push(` GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(${enumName}));`); lines.push(` }`); lines.push(` }`); lines.push(`}`, ""); @@ -1283,7 +1283,7 @@ using System.Diagnostics.CodeAnalysis; using System.Text.Json; using System.Text.Json.Serialization; -namespace GitHub.Copilot.SDK; +namespace GitHub.Copilot; `); // Base class with XML doc @@ -2243,7 +2243,7 @@ using System.Text.Json; using System.Text.Json.Serialization; using System.Threading; -namespace GitHub.Copilot.SDK.Rpc; +namespace GitHub.Copilot.Rpc; `); for (const cls of classes) if (cls) lines.push(cls, ""); @@ -2264,7 +2264,7 @@ namespace GitHub.Copilot.SDK.Rpc; if (schemaFile !== "session-events.schema.json") continue; for (const name of [...names].sort()) { const typeName = typeToClassName(name); - lines.push(`[JsonSerializable(typeof(GitHub.Copilot.SDK.${typeName}), TypeInfoPropertyName = "SessionEvents${typeName}")]`); + lines.push(`[JsonSerializable(typeof(GitHub.Copilot.${typeName}), TypeInfoPropertyName = "SessionEvents${typeName}")]`); } } for (const t of typeNames) lines.push(`[JsonSerializable(typeof(${t}))]`); diff --git a/scripts/docs-validation/extract.ts b/scripts/docs-validation/extract.ts index 879873048..0b0879db1 100644 --- a/scripts/docs-validation/extract.ts +++ b/scripts/docs-validation/extract.ts @@ -302,8 +302,8 @@ function wrapCodeForValidation(block: CodeBlock): string { } // Always ensure SDK using is present - if (!usings.some(u => u.includes("GitHub.Copilot.SDK"))) { - usings.push("using GitHub.Copilot.SDK;"); + if (!usings.some(u => u.includes("GitHub.Copilot"))) { + usings.push("using GitHub.Copilot;"); } // Generate a unique class name based on block location @@ -336,8 +336,8 @@ ${indentedCode} } } else { // Has structure, but may still need using directive - if (!code.includes("using GitHub.Copilot.SDK;")) { - code = "using GitHub.Copilot.SDK;\n" + code; + if (!code.includes("using GitHub.Copilot;")) { + code = "using GitHub.Copilot;\n" + code; } } } diff --git a/test/scenarios/auth/byok-anthropic/csharp/Program.cs b/test/scenarios/auth/byok-anthropic/csharp/Program.cs index 6bb9dd231..f29cfd4d8 100644 --- a/test/scenarios/auth/byok-anthropic/csharp/Program.cs +++ b/test/scenarios/auth/byok-anthropic/csharp/Program.cs @@ -1,4 +1,4 @@ -using GitHub.Copilot.SDK; +using GitHub.Copilot; var apiKey = Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY"); var model = Environment.GetEnvironmentVariable("ANTHROPIC_MODEL") ?? "claude-sonnet-4-20250514"; @@ -12,7 +12,7 @@ using var client = new CopilotClient(new CopilotClientOptions { - CliPath = Environment.GetEnvironmentVariable("COPILOT_CLI_PATH"), + Connection = RuntimeConnection.ForStdio(path: Environment.GetEnvironmentVariable("COPILOT_CLI_PATH")), }); await client.StartAsync(); diff --git a/test/scenarios/auth/byok-azure/csharp/Program.cs b/test/scenarios/auth/byok-azure/csharp/Program.cs index e6b2789a1..64132bbff 100644 --- a/test/scenarios/auth/byok-azure/csharp/Program.cs +++ b/test/scenarios/auth/byok-azure/csharp/Program.cs @@ -1,4 +1,4 @@ -using GitHub.Copilot.SDK; +using GitHub.Copilot; var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT"); var apiKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY"); @@ -13,7 +13,7 @@ using var client = new CopilotClient(new CopilotClientOptions { - CliPath = Environment.GetEnvironmentVariable("COPILOT_CLI_PATH"), + Connection = RuntimeConnection.ForStdio(path: Environment.GetEnvironmentVariable("COPILOT_CLI_PATH")), }); await client.StartAsync(); diff --git a/test/scenarios/auth/byok-ollama/csharp/Program.cs b/test/scenarios/auth/byok-ollama/csharp/Program.cs index 585157b66..69578a378 100644 --- a/test/scenarios/auth/byok-ollama/csharp/Program.cs +++ b/test/scenarios/auth/byok-ollama/csharp/Program.cs @@ -1,4 +1,4 @@ -using GitHub.Copilot.SDK; +using GitHub.Copilot; var baseUrl = Environment.GetEnvironmentVariable("OLLAMA_BASE_URL") ?? "http://localhost:11434/v1"; var model = Environment.GetEnvironmentVariable("OLLAMA_MODEL") ?? "llama3.2:3b"; @@ -8,7 +8,7 @@ using var client = new CopilotClient(new CopilotClientOptions { - CliPath = Environment.GetEnvironmentVariable("COPILOT_CLI_PATH"), + Connection = RuntimeConnection.ForStdio(path: Environment.GetEnvironmentVariable("COPILOT_CLI_PATH")), }); await client.StartAsync(); diff --git a/test/scenarios/auth/byok-openai/csharp/Program.cs b/test/scenarios/auth/byok-openai/csharp/Program.cs index 5d549bd5c..d98cffbc3 100644 --- a/test/scenarios/auth/byok-openai/csharp/Program.cs +++ b/test/scenarios/auth/byok-openai/csharp/Program.cs @@ -1,4 +1,4 @@ -using GitHub.Copilot.SDK; +using GitHub.Copilot; var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY"); var model = Environment.GetEnvironmentVariable("OPENAI_MODEL") ?? "claude-haiku-4.5"; @@ -12,7 +12,7 @@ using var client = new CopilotClient(new CopilotClientOptions { - CliPath = Environment.GetEnvironmentVariable("COPILOT_CLI_PATH"), + Connection = RuntimeConnection.ForStdio(path: Environment.GetEnvironmentVariable("COPILOT_CLI_PATH")), }); await client.StartAsync(); diff --git a/test/scenarios/auth/gh-app/csharp/Program.cs b/test/scenarios/auth/gh-app/csharp/Program.cs index 1f2e27ccf..5933ec087 100644 --- a/test/scenarios/auth/gh-app/csharp/Program.cs +++ b/test/scenarios/auth/gh-app/csharp/Program.cs @@ -1,6 +1,6 @@ using System.Net.Http.Json; using System.Text.Json; -using GitHub.Copilot.SDK; +using GitHub.Copilot; // GitHub OAuth Device Flow var clientId = Environment.GetEnvironmentVariable("GITHUB_OAUTH_CLIENT_ID") @@ -60,7 +60,7 @@ // Step 4: Use the token with Copilot using var client = new CopilotClient(new CopilotClientOptions { - CliPath = Environment.GetEnvironmentVariable("COPILOT_CLI_PATH"), + Connection = RuntimeConnection.ForStdio(path: Environment.GetEnvironmentVariable("COPILOT_CLI_PATH")), GitHubToken = accessToken, }); diff --git a/test/scenarios/bundling/app-backend-to-server/csharp/Program.cs b/test/scenarios/bundling/app-backend-to-server/csharp/Program.cs index df3a335b0..f6e993f0c 100644 --- a/test/scenarios/bundling/app-backend-to-server/csharp/Program.cs +++ b/test/scenarios/bundling/app-backend-to-server/csharp/Program.cs @@ -1,5 +1,5 @@ using System.Text.Json; -using GitHub.Copilot.SDK; +using GitHub.Copilot; var port = Environment.GetEnvironmentVariable("PORT") ?? "8080"; var cliUrl = Environment.GetEnvironmentVariable("CLI_URL") @@ -21,7 +21,7 @@ return; } - using var client = new CopilotClient(new CopilotClientOptions { CliUrl = cliUrl }); + using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(cliUrl) }); await client.StartAsync(); try diff --git a/test/scenarios/bundling/app-direct-server/csharp/Program.cs b/test/scenarios/bundling/app-direct-server/csharp/Program.cs index 6dd14e9db..c7b9fec70 100644 --- a/test/scenarios/bundling/app-direct-server/csharp/Program.cs +++ b/test/scenarios/bundling/app-direct-server/csharp/Program.cs @@ -1,8 +1,8 @@ -using GitHub.Copilot.SDK; +using GitHub.Copilot; var cliUrl = Environment.GetEnvironmentVariable("COPILOT_CLI_URL") ?? "localhost:3000"; -using var client = new CopilotClient(new CopilotClientOptions { CliUrl = cliUrl }); +using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(cliUrl) }); await client.StartAsync(); try diff --git a/test/scenarios/bundling/container-proxy/csharp/Program.cs b/test/scenarios/bundling/container-proxy/csharp/Program.cs index 6dd14e9db..c7b9fec70 100644 --- a/test/scenarios/bundling/container-proxy/csharp/Program.cs +++ b/test/scenarios/bundling/container-proxy/csharp/Program.cs @@ -1,8 +1,8 @@ -using GitHub.Copilot.SDK; +using GitHub.Copilot; var cliUrl = Environment.GetEnvironmentVariable("COPILOT_CLI_URL") ?? "localhost:3000"; -using var client = new CopilotClient(new CopilotClientOptions { CliUrl = cliUrl }); +using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(cliUrl) }); await client.StartAsync(); try diff --git a/test/scenarios/bundling/fully-bundled/csharp/Program.cs b/test/scenarios/bundling/fully-bundled/csharp/Program.cs index cb67c903c..e9dfbcccc 100644 --- a/test/scenarios/bundling/fully-bundled/csharp/Program.cs +++ b/test/scenarios/bundling/fully-bundled/csharp/Program.cs @@ -1,8 +1,8 @@ -using GitHub.Copilot.SDK; +using GitHub.Copilot; using var client = new CopilotClient(new CopilotClientOptions { - CliPath = Environment.GetEnvironmentVariable("COPILOT_CLI_PATH"), + Connection = RuntimeConnection.ForStdio(path: Environment.GetEnvironmentVariable("COPILOT_CLI_PATH")), GitHubToken = Environment.GetEnvironmentVariable("GITHUB_TOKEN"), }); diff --git a/test/scenarios/callbacks/hooks/csharp/Program.cs b/test/scenarios/callbacks/hooks/csharp/Program.cs index 63c15128f..78184df2a 100644 --- a/test/scenarios/callbacks/hooks/csharp/Program.cs +++ b/test/scenarios/callbacks/hooks/csharp/Program.cs @@ -1,10 +1,10 @@ -using GitHub.Copilot.SDK; +using GitHub.Copilot; var hookLog = new List(); using var client = new CopilotClient(new CopilotClientOptions { - CliPath = Environment.GetEnvironmentVariable("COPILOT_CLI_PATH"), + Connection = RuntimeConnection.ForStdio(path: Environment.GetEnvironmentVariable("COPILOT_CLI_PATH")), GitHubToken = Environment.GetEnvironmentVariable("GITHUB_TOKEN"), }); diff --git a/test/scenarios/callbacks/permissions/csharp/Program.cs b/test/scenarios/callbacks/permissions/csharp/Program.cs index 889eeaff1..cf3275e56 100644 --- a/test/scenarios/callbacks/permissions/csharp/Program.cs +++ b/test/scenarios/callbacks/permissions/csharp/Program.cs @@ -1,10 +1,10 @@ -using GitHub.Copilot.SDK; +using GitHub.Copilot; var permissionLog = new List(); using var client = new CopilotClient(new CopilotClientOptions { - CliPath = Environment.GetEnvironmentVariable("COPILOT_CLI_PATH"), + Connection = RuntimeConnection.ForStdio(path: Environment.GetEnvironmentVariable("COPILOT_CLI_PATH")), GitHubToken = Environment.GetEnvironmentVariable("GITHUB_TOKEN"), }); diff --git a/test/scenarios/callbacks/user-input/csharp/Program.cs b/test/scenarios/callbacks/user-input/csharp/Program.cs index 6ad0454d7..e9fe06968 100644 --- a/test/scenarios/callbacks/user-input/csharp/Program.cs +++ b/test/scenarios/callbacks/user-input/csharp/Program.cs @@ -1,10 +1,10 @@ -using GitHub.Copilot.SDK; +using GitHub.Copilot; var inputLog = new List(); using var client = new CopilotClient(new CopilotClientOptions { - CliPath = Environment.GetEnvironmentVariable("COPILOT_CLI_PATH"), + Connection = RuntimeConnection.ForStdio(path: Environment.GetEnvironmentVariable("COPILOT_CLI_PATH")), GitHubToken = Environment.GetEnvironmentVariable("GITHUB_TOKEN"), }); diff --git a/test/scenarios/modes/default/csharp/Program.cs b/test/scenarios/modes/default/csharp/Program.cs index 243fcb922..23d6c63f0 100644 --- a/test/scenarios/modes/default/csharp/Program.cs +++ b/test/scenarios/modes/default/csharp/Program.cs @@ -1,8 +1,8 @@ -using GitHub.Copilot.SDK; +using GitHub.Copilot; using var client = new CopilotClient(new CopilotClientOptions { - CliPath = Environment.GetEnvironmentVariable("COPILOT_CLI_PATH"), + Connection = RuntimeConnection.ForStdio(path: Environment.GetEnvironmentVariable("COPILOT_CLI_PATH")), GitHubToken = Environment.GetEnvironmentVariable("GITHUB_TOKEN"), }); diff --git a/test/scenarios/modes/minimal/csharp/Program.cs b/test/scenarios/modes/minimal/csharp/Program.cs index 94cbc2034..70081b58d 100644 --- a/test/scenarios/modes/minimal/csharp/Program.cs +++ b/test/scenarios/modes/minimal/csharp/Program.cs @@ -1,8 +1,8 @@ -using GitHub.Copilot.SDK; +using GitHub.Copilot; using var client = new CopilotClient(new CopilotClientOptions { - CliPath = Environment.GetEnvironmentVariable("COPILOT_CLI_PATH"), + Connection = RuntimeConnection.ForStdio(path: Environment.GetEnvironmentVariable("COPILOT_CLI_PATH")), GitHubToken = Environment.GetEnvironmentVariable("GITHUB_TOKEN"), }); diff --git a/test/scenarios/prompts/attachments/csharp/Program.cs b/test/scenarios/prompts/attachments/csharp/Program.cs index 272c89aab..7cafcb86d 100644 --- a/test/scenarios/prompts/attachments/csharp/Program.cs +++ b/test/scenarios/prompts/attachments/csharp/Program.cs @@ -1,8 +1,8 @@ -using GitHub.Copilot.SDK; +using GitHub.Copilot; using var client = new CopilotClient(new CopilotClientOptions { - CliPath = Environment.GetEnvironmentVariable("COPILOT_CLI_PATH"), + Connection = RuntimeConnection.ForStdio(path: Environment.GetEnvironmentVariable("COPILOT_CLI_PATH")), GitHubToken = Environment.GetEnvironmentVariable("GITHUB_TOKEN"), }); diff --git a/test/scenarios/prompts/reasoning-effort/csharp/Program.cs b/test/scenarios/prompts/reasoning-effort/csharp/Program.cs index 719650880..2ed2ae94d 100644 --- a/test/scenarios/prompts/reasoning-effort/csharp/Program.cs +++ b/test/scenarios/prompts/reasoning-effort/csharp/Program.cs @@ -1,8 +1,8 @@ -using GitHub.Copilot.SDK; +using GitHub.Copilot; using var client = new CopilotClient(new CopilotClientOptions { - CliPath = Environment.GetEnvironmentVariable("COPILOT_CLI_PATH"), + Connection = RuntimeConnection.ForStdio(path: Environment.GetEnvironmentVariable("COPILOT_CLI_PATH")), GitHubToken = Environment.GetEnvironmentVariable("GITHUB_TOKEN"), }); diff --git a/test/scenarios/prompts/system-message/csharp/Program.cs b/test/scenarios/prompts/system-message/csharp/Program.cs index 5f22cb029..48afdd7ba 100644 --- a/test/scenarios/prompts/system-message/csharp/Program.cs +++ b/test/scenarios/prompts/system-message/csharp/Program.cs @@ -1,10 +1,10 @@ -using GitHub.Copilot.SDK; +using GitHub.Copilot; var piratePrompt = "You are a pirate. Always respond in pirate speak. Say 'Arrr!' in every response. Use nautical terms and pirate slang throughout."; using var client = new CopilotClient(new CopilotClientOptions { - CliPath = Environment.GetEnvironmentVariable("COPILOT_CLI_PATH"), + Connection = RuntimeConnection.ForStdio(path: Environment.GetEnvironmentVariable("COPILOT_CLI_PATH")), GitHubToken = Environment.GetEnvironmentVariable("GITHUB_TOKEN"), }); diff --git a/test/scenarios/sessions/concurrent-sessions/csharp/Program.cs b/test/scenarios/sessions/concurrent-sessions/csharp/Program.cs index 142bcb268..a5ba2577b 100644 --- a/test/scenarios/sessions/concurrent-sessions/csharp/Program.cs +++ b/test/scenarios/sessions/concurrent-sessions/csharp/Program.cs @@ -1,11 +1,11 @@ -using GitHub.Copilot.SDK; +using GitHub.Copilot; const string PiratePrompt = "You are a pirate. Always say Arrr!"; const string RobotPrompt = "You are a robot. Always say BEEP BOOP!"; using var client = new CopilotClient(new CopilotClientOptions { - CliPath = Environment.GetEnvironmentVariable("COPILOT_CLI_PATH"), + Connection = RuntimeConnection.ForStdio(path: Environment.GetEnvironmentVariable("COPILOT_CLI_PATH")), GitHubToken = Environment.GetEnvironmentVariable("GITHUB_TOKEN"), }); diff --git a/test/scenarios/sessions/infinite-sessions/csharp/Program.cs b/test/scenarios/sessions/infinite-sessions/csharp/Program.cs index fe281292d..2619f25b4 100644 --- a/test/scenarios/sessions/infinite-sessions/csharp/Program.cs +++ b/test/scenarios/sessions/infinite-sessions/csharp/Program.cs @@ -1,8 +1,8 @@ -using GitHub.Copilot.SDK; +using GitHub.Copilot; using var client = new CopilotClient(new CopilotClientOptions { - CliPath = Environment.GetEnvironmentVariable("COPILOT_CLI_PATH"), + Connection = RuntimeConnection.ForStdio(path: Environment.GetEnvironmentVariable("COPILOT_CLI_PATH")), GitHubToken = Environment.GetEnvironmentVariable("GITHUB_TOKEN"), }); diff --git a/test/scenarios/sessions/session-resume/csharp/Program.cs b/test/scenarios/sessions/session-resume/csharp/Program.cs index 73979669d..a1bd015bd 100644 --- a/test/scenarios/sessions/session-resume/csharp/Program.cs +++ b/test/scenarios/sessions/session-resume/csharp/Program.cs @@ -1,8 +1,8 @@ -using GitHub.Copilot.SDK; +using GitHub.Copilot; using var client = new CopilotClient(new CopilotClientOptions { - CliPath = Environment.GetEnvironmentVariable("COPILOT_CLI_PATH"), + Connection = RuntimeConnection.ForStdio(path: Environment.GetEnvironmentVariable("COPILOT_CLI_PATH")), GitHubToken = Environment.GetEnvironmentVariable("GITHUB_TOKEN"), }); diff --git a/test/scenarios/sessions/streaming/csharp/Program.cs b/test/scenarios/sessions/streaming/csharp/Program.cs index 01683df76..518d4b227 100644 --- a/test/scenarios/sessions/streaming/csharp/Program.cs +++ b/test/scenarios/sessions/streaming/csharp/Program.cs @@ -1,4 +1,4 @@ -using GitHub.Copilot.SDK; +using GitHub.Copilot; var options = new CopilotClientOptions { @@ -8,7 +8,7 @@ var cliPath = Environment.GetEnvironmentVariable("COPILOT_CLI_PATH"); if (!string.IsNullOrEmpty(cliPath)) { - options.CliPath = cliPath; + options.Connection = RuntimeConnection.ForStdio(path: cliPath); } using var client = new CopilotClient(options); @@ -24,7 +24,7 @@ }); var chunkCount = 0; - using var subscription = session.On(evt => + using var subscription = session.On(evt => { if (evt is AssistantMessageDeltaEvent) { diff --git a/test/scenarios/tools/custom-agents/csharp/Program.cs b/test/scenarios/tools/custom-agents/csharp/Program.cs index d3c068ade..6c5b980cc 100644 --- a/test/scenarios/tools/custom-agents/csharp/Program.cs +++ b/test/scenarios/tools/custom-agents/csharp/Program.cs @@ -1,11 +1,11 @@ -using GitHub.Copilot.SDK; +using GitHub.Copilot; using Microsoft.Extensions.AI; var cliPath = Environment.GetEnvironmentVariable("COPILOT_CLI_PATH"); using var client = new CopilotClient(new CopilotClientOptions { - CliPath = cliPath, + Connection = RuntimeConnection.ForStdio(path: cliPath), GitHubToken = Environment.GetEnvironmentVariable("GITHUB_TOKEN"), }); diff --git a/test/scenarios/tools/mcp-servers/csharp/Program.cs b/test/scenarios/tools/mcp-servers/csharp/Program.cs index e3c1ed428..ed667825f 100644 --- a/test/scenarios/tools/mcp-servers/csharp/Program.cs +++ b/test/scenarios/tools/mcp-servers/csharp/Program.cs @@ -1,8 +1,8 @@ -using GitHub.Copilot.SDK; +using GitHub.Copilot; using var client = new CopilotClient(new CopilotClientOptions { - CliPath = Environment.GetEnvironmentVariable("COPILOT_CLI_PATH"), + Connection = RuntimeConnection.ForStdio(path: Environment.GetEnvironmentVariable("COPILOT_CLI_PATH")), GitHubToken = Environment.GetEnvironmentVariable("GITHUB_TOKEN"), }); diff --git a/test/scenarios/tools/no-tools/csharp/Program.cs b/test/scenarios/tools/no-tools/csharp/Program.cs index c3de1de53..a0ea0eefe 100644 --- a/test/scenarios/tools/no-tools/csharp/Program.cs +++ b/test/scenarios/tools/no-tools/csharp/Program.cs @@ -1,4 +1,4 @@ -using GitHub.Copilot.SDK; +using GitHub.Copilot; const string SystemPrompt = """ You are a minimal assistant with no tools available. @@ -9,7 +9,7 @@ You can only respond with text based on your training data. using var client = new CopilotClient(new CopilotClientOptions { - CliPath = Environment.GetEnvironmentVariable("COPILOT_CLI_PATH"), + Connection = RuntimeConnection.ForStdio(path: Environment.GetEnvironmentVariable("COPILOT_CLI_PATH")), GitHubToken = Environment.GetEnvironmentVariable("GITHUB_TOKEN"), }); diff --git a/test/scenarios/tools/skills/csharp/Program.cs b/test/scenarios/tools/skills/csharp/Program.cs index d0394a396..81adf96a5 100644 --- a/test/scenarios/tools/skills/csharp/Program.cs +++ b/test/scenarios/tools/skills/csharp/Program.cs @@ -1,8 +1,8 @@ -using GitHub.Copilot.SDK; +using GitHub.Copilot; using var client = new CopilotClient(new CopilotClientOptions { - CliPath = Environment.GetEnvironmentVariable("COPILOT_CLI_PATH"), + Connection = RuntimeConnection.ForStdio(path: Environment.GetEnvironmentVariable("COPILOT_CLI_PATH")), GitHubToken = Environment.GetEnvironmentVariable("GITHUB_TOKEN"), }); diff --git a/test/scenarios/tools/tool-filtering/csharp/Program.cs b/test/scenarios/tools/tool-filtering/csharp/Program.cs index f21482b1b..72431c005 100644 --- a/test/scenarios/tools/tool-filtering/csharp/Program.cs +++ b/test/scenarios/tools/tool-filtering/csharp/Program.cs @@ -1,8 +1,8 @@ -using GitHub.Copilot.SDK; +using GitHub.Copilot; using var client = new CopilotClient(new CopilotClientOptions { - CliPath = Environment.GetEnvironmentVariable("COPILOT_CLI_PATH"), + Connection = RuntimeConnection.ForStdio(path: Environment.GetEnvironmentVariable("COPILOT_CLI_PATH")), GitHubToken = Environment.GetEnvironmentVariable("GITHUB_TOKEN"), }); diff --git a/test/scenarios/tools/tool-overrides/csharp/Program.cs b/test/scenarios/tools/tool-overrides/csharp/Program.cs index be8c07ec8..a8c7679de 100644 --- a/test/scenarios/tools/tool-overrides/csharp/Program.cs +++ b/test/scenarios/tools/tool-overrides/csharp/Program.cs @@ -1,10 +1,10 @@ using System.ComponentModel; -using GitHub.Copilot.SDK; +using GitHub.Copilot; using Microsoft.Extensions.AI; using var client = new CopilotClient(new CopilotClientOptions { - CliPath = Environment.GetEnvironmentVariable("COPILOT_CLI_PATH"), + Connection = RuntimeConnection.ForStdio(path: Environment.GetEnvironmentVariable("COPILOT_CLI_PATH")), GitHubToken = Environment.GetEnvironmentVariable("GITHUB_TOKEN"), }); diff --git a/test/scenarios/tools/virtual-filesystem/csharp/Program.cs b/test/scenarios/tools/virtual-filesystem/csharp/Program.cs index d67a3738c..93ad41f1e 100644 --- a/test/scenarios/tools/virtual-filesystem/csharp/Program.cs +++ b/test/scenarios/tools/virtual-filesystem/csharp/Program.cs @@ -1,5 +1,5 @@ using System.ComponentModel; -using GitHub.Copilot.SDK; +using GitHub.Copilot; using Microsoft.Extensions.AI; // In-memory virtual filesystem @@ -7,7 +7,7 @@ using var client = new CopilotClient(new CopilotClientOptions { - CliPath = Environment.GetEnvironmentVariable("COPILOT_CLI_PATH"), + Connection = RuntimeConnection.ForStdio(path: Environment.GetEnvironmentVariable("COPILOT_CLI_PATH")), GitHubToken = Environment.GetEnvironmentVariable("GITHUB_TOKEN"), }); diff --git a/test/scenarios/transport/reconnect/csharp/Program.cs b/test/scenarios/transport/reconnect/csharp/Program.cs index 80dc482da..99e9b91dd 100644 --- a/test/scenarios/transport/reconnect/csharp/Program.cs +++ b/test/scenarios/transport/reconnect/csharp/Program.cs @@ -1,8 +1,8 @@ -using GitHub.Copilot.SDK; +using GitHub.Copilot; var cliUrl = Environment.GetEnvironmentVariable("COPILOT_CLI_URL") ?? "localhost:3000"; -using var client = new CopilotClient(new CopilotClientOptions { CliUrl = cliUrl }); +using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(cliUrl) }); await client.StartAsync(); try diff --git a/test/scenarios/transport/stdio/csharp/Program.cs b/test/scenarios/transport/stdio/csharp/Program.cs index cb67c903c..e9dfbcccc 100644 --- a/test/scenarios/transport/stdio/csharp/Program.cs +++ b/test/scenarios/transport/stdio/csharp/Program.cs @@ -1,8 +1,8 @@ -using GitHub.Copilot.SDK; +using GitHub.Copilot; using var client = new CopilotClient(new CopilotClientOptions { - CliPath = Environment.GetEnvironmentVariable("COPILOT_CLI_PATH"), + Connection = RuntimeConnection.ForStdio(path: Environment.GetEnvironmentVariable("COPILOT_CLI_PATH")), GitHubToken = Environment.GetEnvironmentVariable("GITHUB_TOKEN"), }); diff --git a/test/scenarios/transport/tcp/csharp/Program.cs b/test/scenarios/transport/tcp/csharp/Program.cs index 051c877d2..9fe2b176b 100644 --- a/test/scenarios/transport/tcp/csharp/Program.cs +++ b/test/scenarios/transport/tcp/csharp/Program.cs @@ -1,10 +1,10 @@ -using GitHub.Copilot.SDK; +using GitHub.Copilot; var cliUrl = Environment.GetEnvironmentVariable("COPILOT_CLI_URL") ?? "localhost:3000"; using var client = new CopilotClient(new CopilotClientOptions { - CliUrl = cliUrl, + Connection = RuntimeConnection.ForUri(cliUrl), }); await client.StartAsync();