diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml
index b6650cd57..afe9b03bd 100644
--- a/.github/workflows/copilot-setup-steps.yml
+++ b/.github/workflows/copilot-setup-steps.yml
@@ -58,7 +58,7 @@ jobs:
- name: Set up .NET
uses: actions/setup-dotnet@v5
with:
- dotnet-version: "8.0.x"
+ dotnet-version: "10.0.x"
# Install just command runner
- name: Install just
diff --git a/.github/workflows/docs-validation.yml b/.github/workflows/docs-validation.yml
index 20031af07..89d2fa2a9 100644
--- a/.github/workflows/docs-validation.yml
+++ b/.github/workflows/docs-validation.yml
@@ -108,7 +108,7 @@ jobs:
- uses: actions/setup-dotnet@v5
with:
- dotnet-version: "8.0.x"
+ dotnet-version: "10.0.x"
- name: Install validation dependencies
working-directory: scripts/docs-validation
diff --git a/.github/workflows/dotnet-sdk-tests.yml b/.github/workflows/dotnet-sdk-tests.yml
index bbe577bc1..3ca9d1de9 100644
--- a/.github/workflows/dotnet-sdk-tests.yml
+++ b/.github/workflows/dotnet-sdk-tests.yml
@@ -43,7 +43,7 @@ jobs:
- uses: actions/checkout@v6.0.2
- uses: actions/setup-dotnet@v5
with:
- dotnet-version: "8.0.x"
+ dotnet-version: "10.0.x"
- uses: actions/setup-node@v6
with:
node-version: "22"
diff --git a/.github/workflows/nodejs-sdk-tests.yml b/.github/workflows/nodejs-sdk-tests.yml
index 5947396d0..9e978a22f 100644
--- a/.github/workflows/nodejs-sdk-tests.yml
+++ b/.github/workflows/nodejs-sdk-tests.yml
@@ -12,6 +12,7 @@ on:
- 'nodejs/**'
- 'test/**'
- '.github/workflows/nodejs-sdk-tests.yml'
+ - '!nodejs/scripts/**'
- '!**/*.md'
- '!**/LICENSE*'
- '!**/.gitignore'
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index b855566a5..89df96c9f 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -121,7 +121,7 @@ jobs:
- uses: actions/checkout@v6.0.2
- uses: actions/setup-dotnet@v5
with:
- dotnet-version: "8.0.x"
+ dotnet-version: "10.0.x"
- name: Restore dependencies
run: dotnet restore
- name: Build and pack
diff --git a/.github/workflows/scenario-builds.yml b/.github/workflows/scenario-builds.yml
index a66ede5ec..54d7257e5 100644
--- a/.github/workflows/scenario-builds.yml
+++ b/.github/workflows/scenario-builds.yml
@@ -152,7 +152,7 @@ jobs:
- uses: actions/setup-dotnet@v5
with:
- dotnet-version: "8.0.x"
+ dotnet-version: "10.0.x"
- uses: actions/cache@v4
with:
diff --git a/.github/workflows/update-copilot-dependency.yml b/.github/workflows/update-copilot-dependency.yml
index 34a6c97a2..b1d3cae6d 100644
--- a/.github/workflows/update-copilot-dependency.yml
+++ b/.github/workflows/update-copilot-dependency.yml
@@ -38,7 +38,7 @@ jobs:
- uses: actions/setup-dotnet@v5
with:
- dotnet-version: "8.0.x"
+ dotnet-version: "10.0.x"
- name: Update @github/copilot in nodejs
env:
diff --git a/dotnet/Directory.Build.props b/dotnet/Directory.Build.props
new file mode 100644
index 000000000..badf8483d
--- /dev/null
+++ b/dotnet/Directory.Build.props
@@ -0,0 +1,12 @@
+
+
+
+ net8.0
+ 14
+ enable
+ enable
+ 10.0-minimum
+ true
+
+
+
diff --git a/dotnet/Directory.Packages.props b/dotnet/Directory.Packages.props
new file mode 100644
index 000000000..5447fee51
--- /dev/null
+++ b/dotnet/Directory.Packages.props
@@ -0,0 +1,19 @@
+
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/global.json b/dotnet/global.json
new file mode 100644
index 000000000..c0c9c61a0
--- /dev/null
+++ b/dotnet/global.json
@@ -0,0 +1,6 @@
+{
+ "sdk": {
+ "version": "10.0.100",
+ "rollForward": "major"
+ }
+}
diff --git a/dotnet/nuget.config b/dotnet/nuget.config
new file mode 100644
index 000000000..128d95e59
--- /dev/null
+++ b/dotnet/nuget.config
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/samples/Chat.csproj b/dotnet/samples/Chat.csproj
index 4121ceaef..ad90a6062 100644
--- a/dotnet/samples/Chat.csproj
+++ b/dotnet/samples/Chat.csproj
@@ -1,9 +1,6 @@
Exe
- net8.0
- enable
- enable
diff --git a/dotnet/src/Client.cs b/dotnet/src/Client.cs
index cf6c5a29d..223935e7f 100644
--- a/dotnet/src/Client.cs
+++ b/dotnet/src/Client.cs
@@ -16,6 +16,7 @@
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
using GitHub.Copilot.SDK.Rpc;
+using System.Globalization;
namespace GitHub.Copilot.SDK;
@@ -51,7 +52,7 @@ namespace GitHub.Copilot.SDK;
/// await session.SendAsync(new MessageOptions { Prompt = "Hello!" });
///
///
-public partial class CopilotClient : IDisposable, IAsyncDisposable
+public sealed partial class CopilotClient : IDisposable, IAsyncDisposable
{
private readonly ConcurrentDictionary _sessions = new();
private readonly CopilotClientOptions _options;
@@ -62,8 +63,8 @@ public partial class CopilotClient : IDisposable, IAsyncDisposable
private readonly string? _optionsHost;
private List? _modelsCache;
private readonly SemaphoreSlim _modelsCacheLock = new(1, 1);
- private readonly List> _lifecycleHandlers = new();
- private readonly Dictionary>> _typedLifecycleHandlers = new();
+ private readonly List> _lifecycleHandlers = [];
+ private readonly Dictionary>> _typedLifecycleHandlers = [];
private readonly object _lifecycleHandlersLock = new();
private ServerRpc? _rpc;
@@ -241,7 +242,7 @@ public async Task StopAsync()
}
catch (Exception ex)
{
- errors.Add(new Exception($"Failed to destroy session {session.SessionId}: {ex.Message}", ex));
+ errors.Add(new IOException($"Failed to destroy session {session.SessionId}: {ex.Message}", ex));
}
}
@@ -611,7 +612,7 @@ public async Task> ListModelsAsync(CancellationToken cancellatio
// Check cache (already inside lock)
if (_modelsCache is not null)
{
- return new List(_modelsCache); // Return a copy to prevent cache mutation
+ return [.. _modelsCache]; // Return a copy to prevent cache mutation
}
// Cache miss - fetch from backend while holding lock
@@ -621,7 +622,7 @@ public async Task> ListModelsAsync(CancellationToken cancellatio
// Update cache before releasing lock
_modelsCache = response.Models;
- return new List(response.Models); // Return a copy to prevent cache mutation
+ return [.. response.Models]; // Return a copy to prevent cache mutation
}
finally
{
@@ -820,7 +821,7 @@ public IDisposable On(string eventType, Action handler)
{
if (!_typedLifecycleHandlers.TryGetValue(eventType, out var handlers))
{
- handlers = new List>();
+ handlers = [];
_typedLifecycleHandlers[eventType] = handlers;
}
handlers.Add(handler);
@@ -846,9 +847,9 @@ private void DispatchLifecycleEvent(SessionLifecycleEvent evt)
lock (_lifecycleHandlersLock)
{
typedHandlers = _typedLifecycleHandlers.TryGetValue(evt.Type, out var handlers)
- ? new List>(handlers)
- : new List>();
- wildcardHandlers = new List>(_lifecycleHandlers);
+ ? [.. handlers]
+ : [];
+ wildcardHandlers = [.. _lifecycleHandlers];
}
foreach (var handler in typedHandlers)
@@ -907,7 +908,7 @@ private Task EnsureConnectedAsync(CancellationToken cancellationToke
return (Task)StartAsync(cancellationToken);
}
- private async Task VerifyProtocolVersionAsync(Connection connection, CancellationToken cancellationToken)
+ private static async Task VerifyProtocolVersionAsync(Connection connection, CancellationToken cancellationToken)
{
var expectedVersion = SdkProtocolVersion.GetVersion();
var pingResponse = await InvokeRpcAsync(
@@ -950,7 +951,7 @@ private async Task VerifyProtocolVersionAsync(Connection connection, Cancellatio
}
else if (options.Port > 0)
{
- args.AddRange(["--port", options.Port.ToString()]);
+ args.AddRange(["--port", options.Port.ToString(CultureInfo.InvariantCulture)]);
}
// Add auth-related flags
@@ -1013,7 +1014,11 @@ private async Task VerifyProtocolVersionAsync(Connection connection, Cancellatio
{
stderrBuffer.AppendLine(line);
}
- logger.LogDebug("[CLI] {Line}", line);
+
+ if (logger.IsEnabled(LogLevel.Debug))
+ {
+ logger.LogDebug("[CLI] {Line}", line);
+ }
}
}
}, cancellationToken);
@@ -1027,13 +1032,10 @@ private async Task VerifyProtocolVersionAsync(Connection connection, Cancellatio
while (!cts.Token.IsCancellationRequested)
{
- var line = await cliProcess.StandardOutput.ReadLineAsync(cts.Token);
- if (line == null) throw new Exception("CLI process exited unexpectedly");
-
- var match = Regex.Match(line, @"listening on port (\d+)", RegexOptions.IgnoreCase);
- if (match.Success)
+ var line = await cliProcess.StandardOutput.ReadLineAsync(cts.Token) ?? throw new IOException("CLI process exited unexpectedly");
+ if (ListeningOnPortRegex().Match(line) is { Success: true } match)
{
- detectedLocalhostTcpPort = int.Parse(match.Groups[1].Value);
+ detectedLocalhostTcpPort = int.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture);
break;
}
}
@@ -1133,8 +1135,10 @@ private async Task ConnectToServerAsync(Process? cliProcess, string?
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Using happy path from https://microsoft.github.io/vs-streamjsonrpc/docs/nativeAOT.html")]
[UnconditionalSuppressMessage("AOT", "IL3050", Justification = "Using happy path from https://microsoft.github.io/vs-streamjsonrpc/docs/nativeAOT.html")]
- private static SystemTextJsonFormatter CreateSystemTextJsonFormatter() =>
- new SystemTextJsonFormatter() { JsonSerializerOptions = SerializerOptionsForMessageFormatter };
+ private static SystemTextJsonFormatter CreateSystemTextJsonFormatter()
+ {
+ return new() { JsonSerializerOptions = SerializerOptionsForMessageFormatter };
+ }
private static JsonSerializerOptions SerializerOptionsForMessageFormatter { get; } = CreateSerializerOptions();
@@ -1157,8 +1161,10 @@ private static JsonSerializerOptions CreateSerializerOptions()
return options;
}
- internal CopilotSession? GetSession(string sessionId) =>
- _sessions.TryGetValue(sessionId, out var session) ? session : null;
+ internal CopilotSession? GetSession(string sessionId)
+ {
+ return _sessions.TryGetValue(sessionId, out var session) ? session : null;
+ }
///
/// Disposes the synchronously.
@@ -1168,7 +1174,7 @@ private static JsonSerializerOptions CreateSerializerOptions()
///
public void Dispose()
{
- DisposeAsync().GetAwaiter().GetResult();
+ DisposeAsync().AsTask().GetAwaiter().GetResult();
}
///
@@ -1223,12 +1229,7 @@ public async Task OnToolCall(string sessionId,
string toolName,
object? arguments)
{
- var session = client.GetSession(sessionId);
- if (session == null)
- {
- throw new ArgumentException($"Unknown session {sessionId}");
- }
-
+ var session = client.GetSession(sessionId) ?? throw new ArgumentException($"Unknown session {sessionId}");
if (session.GetTool(toolName) is not { } tool)
{
return new ToolCallResponse(new ToolResultObject
@@ -1335,12 +1336,7 @@ public async Task OnPermissionRequest(string sessionI
public async Task OnUserInputRequest(string sessionId, string question, List? choices = null, bool? allowFreeform = null)
{
- var session = client.GetSession(sessionId);
- if (session == null)
- {
- throw new ArgumentException($"Unknown session {sessionId}");
- }
-
+ var session = client.GetSession(sessionId) ?? throw new ArgumentException($"Unknown session {sessionId}");
var request = new UserInputRequest
{
Question = question,
@@ -1354,12 +1350,7 @@ public async Task OnUserInputRequest(string sessionId,
public async Task OnHooksInvoke(string sessionId, string hookType, JsonElement input)
{
- var session = client.GetSession(sessionId);
- if (session == null)
- {
- throw new ArgumentException($"Unknown session {sessionId}");
- }
-
+ var session = client.GetSession(sessionId) ?? throw new ArgumentException($"Unknown session {sessionId}");
var output = await session.HandleHooksInvokeAsync(hookType, input);
return new HooksInvokeResponse(output);
}
@@ -1499,33 +1490,70 @@ public LoggerTraceSource(ILogger logger) : base(nameof(LoggerTraceSource), Sourc
private sealed class LoggerTraceListener(ILogger logger) : TraceListener
{
- public override void TraceEvent(TraceEventCache? eventCache, string source, TraceEventType eventType, int id, string? message) =>
- logger.Log(MapLevel(eventType), "[{Source}] {Message}", source, message);
+ public override void TraceEvent(TraceEventCache? eventCache, string source, TraceEventType eventType, int id, string? message)
+ {
+ LogLevel level = MapLevel(eventType);
+ if (logger.IsEnabled(level))
+ {
+ logger.Log(level, "[{Source}] {Message}", source, message);
+ }
+ }
- public override void TraceEvent(TraceEventCache? eventCache, string source, TraceEventType eventType, int id, string? format, params object?[]? args) =>
- logger.Log(MapLevel(eventType), "[{Source}] {Message}", source, args is null || args.Length == 0 ? format : string.Format(format ?? "", args));
+ public override void TraceEvent(TraceEventCache? eventCache, string source, TraceEventType eventType, int id, string? format, params object?[]? args)
+ {
+ LogLevel level = MapLevel(eventType);
+ if (logger.IsEnabled(level))
+ {
+ logger.Log(level, "[{Source}] {Message}", source, args is null || args.Length == 0 ? format : string.Format(CultureInfo.InvariantCulture, format ?? "", args));
+ }
+ }
- public override void TraceData(TraceEventCache? eventCache, string source, TraceEventType eventType, int id, object? data) =>
- logger.Log(MapLevel(eventType), "[{Source}] {Data}", source, data);
+ public override void TraceData(TraceEventCache? eventCache, string source, TraceEventType eventType, int id, object? data)
+ {
+ LogLevel level = MapLevel(eventType);
+ if (logger.IsEnabled(level))
+ {
+ logger.Log(level, "[{Source}] {Data}", source, data);
+ }
+ }
- public override void TraceData(TraceEventCache? eventCache, string source, TraceEventType eventType, int id, params object?[]? data) =>
- logger.Log(MapLevel(eventType), "[{Source}] {Data}", source, data is null ? null : string.Join(", ", data));
+ public override void TraceData(TraceEventCache? eventCache, string source, TraceEventType eventType, int id, params object?[]? data)
+ {
+ LogLevel level = MapLevel(eventType);
+ if (logger.IsEnabled(level))
+ {
+ logger.Log(level, "[{Source}] {Data}", source, data is null ? null : string.Join(", ", data));
+ }
+ }
- public override void Write(string? message) =>
- logger.LogTrace("{Message}", message);
+ public override void Write(string? message)
+ {
+ if (logger.IsEnabled(LogLevel.Trace))
+ {
+ logger.LogTrace("{Message}", message);
+ }
+ }
- public override void WriteLine(string? message) =>
- logger.LogTrace("{Message}", message);
+ public override void WriteLine(string? message)
+ {
+ if (logger.IsEnabled(LogLevel.Trace))
+ {
+ logger.LogTrace("{Message}", message);
+ }
+ }
- private static LogLevel MapLevel(TraceEventType eventType) => eventType switch
+ private static LogLevel MapLevel(TraceEventType eventType)
{
- TraceEventType.Critical => LogLevel.Critical,
- TraceEventType.Error => LogLevel.Error,
- TraceEventType.Warning => LogLevel.Warning,
- TraceEventType.Information => LogLevel.Information,
- TraceEventType.Verbose => LogLevel.Debug,
- _ => LogLevel.Trace
- };
+ return eventType switch
+ {
+ TraceEventType.Critical => LogLevel.Critical,
+ TraceEventType.Error => LogLevel.Error,
+ TraceEventType.Warning => LogLevel.Warning,
+ TraceEventType.Information => LogLevel.Information,
+ TraceEventType.Verbose => LogLevel.Debug,
+ _ => LogLevel.Trace
+ };
+ }
}
}
@@ -1558,11 +1586,20 @@ public override void WriteLine(string? message) =>
[JsonSerializable(typeof(UserInputRequest))]
[JsonSerializable(typeof(UserInputResponse))]
internal partial class ClientJsonContext : JsonSerializerContext;
+
+ [GeneratedRegex(@"listening on port ([0-9]+)", RegexOptions.IgnoreCase)]
+ private static partial Regex ListeningOnPortRegex();
}
-// Must inherit from AIContent as a signal to MEAI to avoid JSON-serializing the
-// value before passing it back to us
+///
+/// Wraps a as to pass structured tool results
+/// back through Microsoft.Extensions.AI without JSON serialization.
+///
+/// The tool result to wrap.
public class ToolResultAIContent(ToolResultObject toolResult) : AIContent
{
+ ///
+ /// Gets the underlying .
+ ///
public ToolResultObject Result => toolResult;
}
diff --git a/dotnet/src/Generated/Rpc.cs b/dotnet/src/Generated/Rpc.cs
index 73d6e769d..4c4bac0f3 100644
--- a/dotnet/src/Generated/Rpc.cs
+++ b/dotnet/src/Generated/Rpc.cs
@@ -5,6 +5,9 @@
// AUTO-GENERATED FILE - DO NOT EDIT
// Generated from: api.schema.json
+// Generated code does not have XML doc comments; suppress CS1591 to avoid warnings.
+#pragma warning disable CS1591
+
using System.Text.Json;
using System.Text.Json.Serialization;
using StreamJsonRpc;
@@ -116,7 +119,7 @@ public class ModelsListResult
{
/// List of available models with full metadata
[JsonPropertyName("models")]
- public List Models { get; set; } = new();
+ public List Models { get; set; } = [];
}
public class Tool
@@ -146,7 +149,7 @@ public class ToolsListResult
{
/// List of available built-in tools with metadata
[JsonPropertyName("tools")]
- public List Tools { get; set; } = new();
+ public List Tools { get; set; } = [];
}
internal class ToolsListRequest
@@ -186,7 +189,7 @@ public class AccountGetQuotaResult
{
/// Quota snapshots keyed by type (e.g., chat, completions, premium_interactions)
[JsonPropertyName("quotaSnapshots")]
- public Dictionary QuotaSnapshots { get; set; } = new();
+ public Dictionary QuotaSnapshots { get; set; } = [];
}
public class SessionModelGetCurrentResult
@@ -289,7 +292,7 @@ public class SessionWorkspaceListFilesResult
{
/// Relative file paths in the workspace files directory
[JsonPropertyName("files")]
- public List Files { get; set; } = new();
+ public List Files { get; set; } = [];
}
internal class SessionWorkspaceListFilesRequest
@@ -365,7 +368,7 @@ public class SessionAgentListResult
{
/// Available custom agents
[JsonPropertyName("agents")]
- public List Agents { get; set; } = new();
+ public List Agents { get; set; } = [];
}
internal class SessionAgentListRequest
diff --git a/dotnet/src/Generated/SessionEvents.cs b/dotnet/src/Generated/SessionEvents.cs
index 25a65f3f7..6de4d0ef3 100644
--- a/dotnet/src/Generated/SessionEvents.cs
+++ b/dotnet/src/Generated/SessionEvents.cs
@@ -5,6 +5,9 @@
// AUTO-GENERATED FILE - DO NOT EDIT
// Generated from: session-events.schema.json
+// Generated code does not have XML doc comments; suppress CS1591 to avoid warnings.
+#pragma warning disable CS1591
+
using System.Text.Json;
using System.Text.Json.Serialization;
diff --git a/dotnet/src/GitHub.Copilot.SDK.csproj b/dotnet/src/GitHub.Copilot.SDK.csproj
index 019788cfa..8ae53ca74 100644
--- a/dotnet/src/GitHub.Copilot.SDK.csproj
+++ b/dotnet/src/GitHub.Copilot.SDK.csproj
@@ -1,20 +1,26 @@
- net8.0
- enable
- enable
- true
+ true
0.1.0
SDK for programmatic control of GitHub Copilot CLI
GitHub
GitHub
Copyright (c) Microsoft Corporation. All rights reserved.
MIT
+ https://github.com/github/copilot-sdk
README.md
https://github.com/github/copilot-sdk
github;copilot;sdk;jsonrpc;agent
true
+ true
+ snupkg
+ true
+ true
+
+
+
+ true
@@ -22,10 +28,11 @@
-
-
-
-
+
+
+
+
+
diff --git a/dotnet/src/SdkProtocolVersion.cs b/dotnet/src/SdkProtocolVersion.cs
index bb47dfebf..b4c2a367f 100644
--- a/dotnet/src/SdkProtocolVersion.cs
+++ b/dotnet/src/SdkProtocolVersion.cs
@@ -11,10 +11,13 @@ internal static class SdkProtocolVersion
///
/// The SDK protocol version.
///
- public const int Version = 2;
+ private const int Version = 2;
///
/// Gets the SDK protocol version.
///
- public static int GetVersion() => Version;
+ public static int GetVersion()
+ {
+ return Version;
+ }
}
diff --git a/dotnet/src/Session.cs b/dotnet/src/Session.cs
index 8bbee6071..68b02b7c0 100644
--- a/dotnet/src/Session.cs
+++ b/dotnet/src/Session.cs
@@ -42,7 +42,7 @@ namespace GitHub.Copilot.SDK;
/// await session.SendAndWaitAsync(new MessageOptions { Prompt = "Hello, world!" });
///
///
-public partial class CopilotSession : IAsyncDisposable
+public sealed partial class CopilotSession : IAsyncDisposable
{
///
/// Multicast delegate used as a thread-safe, insertion-ordered handler list.
@@ -50,8 +50,8 @@ public partial class CopilotSession : IAsyncDisposable
/// Dispatch reads the field once (inherent snapshot, no allocation).
/// Expected handler count is small (typically 1–3), so Delegate.Combine/Remove cost is negligible.
///
- private event SessionEventHandler? _eventHandlers;
- private readonly Dictionary _toolHandlers = new();
+ private event SessionEventHandler? EventHandlers;
+ private readonly Dictionary _toolHandlers = [];
private readonly JsonRpc _rpc;
private volatile PermissionRequestHandler? _permissionHandler;
private volatile UserInputHandler? _userInputHandler;
@@ -96,8 +96,10 @@ internal CopilotSession(string sessionId, JsonRpc rpc, string? workspacePath = n
WorkspacePath = workspacePath;
}
- private Task InvokeRpcAsync(string method, object?[]? args, CancellationToken cancellationToken) =>
- CopilotClient.InvokeRpcAsync(_rpc, method, args, cancellationToken);
+ private Task InvokeRpcAsync(string method, object?[]? args, CancellationToken cancellationToken)
+ {
+ return CopilotClient.InvokeRpcAsync(_rpc, method, args, cancellationToken);
+ }
///
/// Sends a message to the Copilot session and waits for the response.
@@ -249,8 +251,8 @@ void Handler(SessionEvent evt)
///
public IDisposable On(SessionEventHandler handler)
{
- _eventHandlers += handler;
- return new ActionDisposable(() => _eventHandlers -= handler);
+ EventHandlers += handler;
+ return new ActionDisposable(() => EventHandlers -= handler);
}
///
@@ -263,7 +265,7 @@ public IDisposable On(SessionEventHandler handler)
internal void DispatchEvent(SessionEvent sessionEvent)
{
// Reading the field once gives us a snapshot; delegates are immutable.
- _eventHandlers?.Invoke(sessionEvent);
+ EventHandlers?.Invoke(sessionEvent);
}
///
@@ -288,8 +290,10 @@ internal void RegisterTools(ICollection tools)
///
/// The name of the tool to retrieve.
/// The tool if found; otherwise, null.
- internal AIFunction? GetTool(string name) =>
- _toolHandlers.TryGetValue(name, out var tool) ? tool : null;
+ internal AIFunction? GetTool(string name)
+ {
+ return _toolHandlers.TryGetValue(name, out var tool) ? tool : null;
+ }
///
/// Registers a handler for permission requests.
@@ -348,13 +352,7 @@ internal void RegisterUserInputHandler(UserInputHandler handler)
/// A task that resolves with the user's response.
internal async Task HandleUserInputRequestAsync(UserInputRequest request)
{
- var handler = _userInputHandler;
-
- if (handler == null)
- {
- throw new InvalidOperationException("No user input handler registered");
- }
-
+ var handler = _userInputHandler ?? throw new InvalidOperationException("No user input handler registered");
var invocation = new UserInputInvocation
{
SessionId = SessionId
@@ -569,7 +567,7 @@ await InvokeRpcAsync
public class HookInvocation
{
+ ///
+ /// Identifier of the session that triggered the hook.
+ ///
public string SessionId { get; set; } = string.Empty;
}
@@ -254,15 +409,27 @@ public class HookInvocation
///
public class PreToolUseHookInput
{
+ ///
+ /// Unix timestamp in milliseconds when the tool use was initiated.
+ ///
[JsonPropertyName("timestamp")]
public long Timestamp { get; set; }
+ ///
+ /// Current working directory of the session.
+ ///
[JsonPropertyName("cwd")]
public string Cwd { get; set; } = string.Empty;
+ ///
+ /// Name of the tool about to be executed.
+ ///
[JsonPropertyName("toolName")]
public string ToolName { get; set; } = string.Empty;
+ ///
+ /// Arguments that will be passed to the tool.
+ ///
[JsonPropertyName("toolArgs")]
public object? ToolArgs { get; set; }
}
@@ -273,24 +440,44 @@ public class PreToolUseHookInput
public class PreToolUseHookOutput
{
///
- /// Permission decision: "allow", "deny", or "ask".
+ /// Permission decision for the pending tool call.
+ ///
+ /// - "allow" — permit the tool to execute.
+ /// - "deny" — block the tool from executing.
+ /// - "ask" — fall through to the normal permission prompt.
+ ///
///
[JsonPropertyName("permissionDecision")]
public string? PermissionDecision { get; set; }
+ ///
+ /// Human-readable reason for the permission decision.
+ ///
[JsonPropertyName("permissionDecisionReason")]
public string? PermissionDecisionReason { get; set; }
+ ///
+ /// Modified arguments to pass to the tool instead of the original ones.
+ ///
[JsonPropertyName("modifiedArgs")]
public object? ModifiedArgs { get; set; }
+ ///
+ /// Additional context to inject into the conversation for the language model.
+ ///
[JsonPropertyName("additionalContext")]
public string? AdditionalContext { get; set; }
+ ///
+ /// Whether to suppress the tool's output from the conversation.
+ ///
[JsonPropertyName("suppressOutput")]
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);
///
@@ -298,18 +485,33 @@ public class PreToolUseHookOutput
///
public class PostToolUseHookInput
{
+ ///
+ /// Unix timestamp in milliseconds when the tool execution completed.
+ ///
[JsonPropertyName("timestamp")]
public long Timestamp { get; set; }
+ ///
+ /// Current working directory of the session.
+ ///
[JsonPropertyName("cwd")]
public string Cwd { get; set; } = string.Empty;
+ ///
+ /// Name of the tool that was executed.
+ ///
[JsonPropertyName("toolName")]
public string ToolName { get; set; } = string.Empty;
+ ///
+ /// Arguments that were passed to the tool.
+ ///
[JsonPropertyName("toolArgs")]
public object? ToolArgs { get; set; }
+ ///
+ /// Result returned by the tool execution.
+ ///
[JsonPropertyName("toolResult")]
public object? ToolResult { get; set; }
}
@@ -319,16 +521,28 @@ public class PostToolUseHookInput
///
public class PostToolUseHookOutput
{
+ ///
+ /// Modified result to replace the original tool result.
+ ///
[JsonPropertyName("modifiedResult")]
public object? ModifiedResult { get; set; }
+ ///
+ /// Additional context to inject into the conversation for the language model.
+ ///
[JsonPropertyName("additionalContext")]
public string? AdditionalContext { get; set; }
+ ///
+ /// Whether to suppress the tool's output from the conversation.
+ ///
[JsonPropertyName("suppressOutput")]
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);
///
@@ -336,12 +550,21 @@ public class PostToolUseHookOutput
///
public class UserPromptSubmittedHookInput
{
+ ///
+ /// Unix timestamp in milliseconds when the prompt was submitted.
+ ///
[JsonPropertyName("timestamp")]
public long Timestamp { get; set; }
+ ///
+ /// Current working directory of the session.
+ ///
[JsonPropertyName("cwd")]
public string Cwd { get; set; } = string.Empty;
+ ///
+ /// The user's prompt text.
+ ///
[JsonPropertyName("prompt")]
public string Prompt { get; set; } = string.Empty;
}
@@ -351,16 +574,28 @@ public class UserPromptSubmittedHookInput
///
public class UserPromptSubmittedHookOutput
{
+ ///
+ /// Modified prompt to use instead of the original user prompt.
+ ///
[JsonPropertyName("modifiedPrompt")]
public string? ModifiedPrompt { get; set; }
+ ///
+ /// Additional context to inject into the conversation for the language model.
+ ///
[JsonPropertyName("additionalContext")]
public string? AdditionalContext { get; set; }
+ ///
+ /// Whether to suppress the prompt's output from the conversation.
+ ///
[JsonPropertyName("suppressOutput")]
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);
///
@@ -368,18 +603,32 @@ public class UserPromptSubmittedHookOutput
///
public class SessionStartHookInput
{
+ ///
+ /// Unix timestamp in milliseconds when the session started.
+ ///
[JsonPropertyName("timestamp")]
public long Timestamp { get; set; }
+ ///
+ /// Current working directory of the session.
+ ///
[JsonPropertyName("cwd")]
public string Cwd { get; set; } = string.Empty;
///
- /// Source of the session start: "startup", "resume", or "new".
+ /// Source of the session start.
+ ///
+ /// - "startup" — initial application startup.
+ /// - "resume" — resuming a previous session.
+ /// - "new" — starting a brand new session.
+ ///
///
[JsonPropertyName("source")]
public string Source { get; set; } = string.Empty;
+ ///
+ /// Initial prompt provided when the session was started.
+ ///
[JsonPropertyName("initialPrompt")]
public string? InitialPrompt { get; set; }
}
@@ -389,13 +638,22 @@ public class SessionStartHookInput
///
public class SessionStartHookOutput
{
+ ///
+ /// Additional context to inject into the session for the language model.
+ ///
[JsonPropertyName("additionalContext")]
public string? AdditionalContext { get; set; }
+ ///
+ /// Modified session configuration to apply at startup.
+ ///
[JsonPropertyName("modifiedConfig")]
public Dictionary? ModifiedConfig { get; set; }
}
+///
+/// Delegate invoked when a session starts, allowing injection of context or config changes.
+///
public delegate Task SessionStartHandler(SessionStartHookInput input, HookInvocation invocation);
///
@@ -403,21 +661,40 @@ public class SessionStartHookOutput
///
public class SessionEndHookInput
{
+ ///
+ /// Unix timestamp in milliseconds when the session ended.
+ ///
[JsonPropertyName("timestamp")]
public long Timestamp { get; set; }
+ ///
+ /// Current working directory of the session.
+ ///
[JsonPropertyName("cwd")]
public string Cwd { get; set; } = string.Empty;
///
- /// Reason for session end: "complete", "error", "abort", "timeout", or "user_exit".
+ /// Reason for session end.
+ ///
+ /// - "complete" — the session finished normally.
+ /// - "error" — the session ended due to an error.
+ /// - "abort" — the session was aborted.
+ /// - "timeout" — the session timed out.
+ /// - "user_exit" — the user exited the session.
+ ///
///
[JsonPropertyName("reason")]
public string Reason { get; set; } = string.Empty;
+ ///
+ /// Final message from the assistant before the session ended.
+ ///
[JsonPropertyName("finalMessage")]
public string? FinalMessage { get; set; }
+ ///
+ /// Error message if the session ended due to an error.
+ ///
[JsonPropertyName("error")]
public string? Error { get; set; }
}
@@ -427,16 +704,28 @@ public class SessionEndHookInput
///
public class SessionEndHookOutput
{
+ ///
+ /// Whether to suppress the session end output from the conversation.
+ ///
[JsonPropertyName("suppressOutput")]
public bool? SuppressOutput { get; set; }
+ ///
+ /// List of cleanup action identifiers to execute after the session ends.
+ ///
[JsonPropertyName("cleanupActions")]
public List? CleanupActions { get; set; }
+ ///
+ /// Summary of the session to persist for future reference.
+ ///
[JsonPropertyName("sessionSummary")]
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);
///
@@ -444,21 +733,39 @@ public class SessionEndHookOutput
///
public class ErrorOccurredHookInput
{
+ ///
+ /// Unix timestamp in milliseconds when the error occurred.
+ ///
[JsonPropertyName("timestamp")]
public long Timestamp { get; set; }
+ ///
+ /// Current working directory of the session.
+ ///
[JsonPropertyName("cwd")]
public string Cwd { get; set; } = string.Empty;
+ ///
+ /// Error message describing what went wrong.
+ ///
[JsonPropertyName("error")]
public string Error { get; set; } = string.Empty;
///
- /// Context of the error: "model_call", "tool_execution", "system", or "user_input".
+ /// Context of the error.
+ ///
+ /// - "model_call" — error during a model API call.
+ /// - "tool_execution" — error during tool execution.
+ /// - "system" — internal system error.
+ /// - "user_input" — error processing user input.
+ ///
///
[JsonPropertyName("errorContext")]
public string ErrorContext { get; set; } = string.Empty;
+ ///
+ /// Whether the error is recoverable and the session can continue.
+ ///
[JsonPropertyName("recoverable")]
public bool Recoverable { get; set; }
}
@@ -468,22 +775,39 @@ public class ErrorOccurredHookInput
///
public class ErrorOccurredHookOutput
{
+ ///
+ /// Whether to suppress the error output from the conversation.
+ ///
[JsonPropertyName("suppressOutput")]
public bool? SuppressOutput { get; set; }
///
- /// Error handling strategy: "retry", "skip", or "abort".
+ /// Error handling strategy.
+ ///
+ /// - "retry" — retry the failed operation.
+ /// - "skip" — skip the failed operation and continue.
+ /// - "abort" — abort the session.
+ ///
///
[JsonPropertyName("errorHandling")]
public string? ErrorHandling { get; set; }
+ ///
+ /// Number of times to retry the failed operation.
+ ///
[JsonPropertyName("retryCount")]
public int? RetryCount { get; set; }
+ ///
+ /// Message to display to the user about the error.
+ ///
[JsonPropertyName("userNotification")]
public string? UserNotification { get; set; }
}
+///
+/// Delegate invoked when an error occurs, allowing custom error handling strategies.
+///
public delegate Task ErrorOccurredHandler(ErrorOccurredHookInput input, HookInvocation invocation);
///
@@ -522,32 +846,61 @@ public class SessionHooks
public ErrorOccurredHandler? OnErrorOccurred { get; set; }
}
+///
+/// Specifies how a custom system message is applied to the session.
+///
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum SystemMessageMode
{
+ /// Append the custom system message to the default system message.
[JsonStringEnumMemberName("append")]
Append,
+ /// Replace the default system message entirely.
[JsonStringEnumMemberName("replace")]
Replace
}
+///
+/// Configuration for the system message used in a session.
+///
public class SystemMessageConfig
{
+ ///
+ /// How the system message is applied (append or replace).
+ ///
public SystemMessageMode? Mode { get; set; }
+ ///
+ /// Content of the system message.
+ ///
public string? Content { get; set; }
}
+///
+/// Configuration for a custom model provider.
+///
public class ProviderConfig
{
+ ///
+ /// Provider type identifier (e.g., "openai", "azure").
+ ///
[JsonPropertyName("type")]
public string? Type { get; set; }
+ ///
+ /// Wire API format to use (e.g., "chat-completions").
+ ///
[JsonPropertyName("wireApi")]
public string? WireApi { get; set; }
+ ///
+ /// Base URL of the provider's API endpoint.
+ ///
[JsonPropertyName("baseUrl")]
public string BaseUrl { get; set; } = string.Empty;
+ ///
+ /// API key for authenticating with the provider.
+ ///
[JsonPropertyName("apiKey")]
public string? ApiKey { get; set; }
@@ -559,12 +912,21 @@ public class ProviderConfig
[JsonPropertyName("bearerToken")]
public string? BearerToken { get; set; }
+ ///
+ /// Azure-specific configuration options.
+ ///
[JsonPropertyName("azure")]
public AzureOptions? Azure { get; set; }
}
+///
+/// Azure OpenAI-specific provider options.
+///
public class AzureOptions
{
+ ///
+ /// Azure OpenAI API version to use (e.g., "2024-02-01").
+ ///
[JsonPropertyName("apiVersion")]
public string? ApiVersion { get; set; }
}
@@ -582,7 +944,7 @@ public class McpLocalServerConfig
/// List of tools to include from this server. Empty list means none. Use "*" for all.
///
[JsonPropertyName("tools")]
- public List Tools { get; set; } = new();
+ public List Tools { get; set; } = [];
///
/// Server type. Defaults to "local".
@@ -606,7 +968,7 @@ public class McpLocalServerConfig
/// Arguments to pass to the command.
///
[JsonPropertyName("args")]
- public List Args { get; set; } = new();
+ public List Args { get; set; } = [];
///
/// Environment variables to pass to the server.
@@ -630,7 +992,7 @@ public class McpRemoteServerConfig
/// List of tools to include from this server. Empty list means none. Use "*" for all.
///
[JsonPropertyName("tools")]
- public List Tools { get; set; } = new();
+ public List Tools { get; set; } = [];
///
/// Server type. Must be "http" or "sse".
@@ -739,6 +1101,9 @@ public class InfiniteSessionConfig
public double? BufferExhaustionThreshold { get; set; }
}
+///
+/// Configuration options for creating a new Copilot session.
+///
public class SessionConfig
{
///
@@ -778,6 +1143,9 @@ protected SessionConfig(SessionConfig? other)
WorkingDirectory = other.WorkingDirectory;
}
+ ///
+ /// Optional session identifier; a new ID is generated if not provided.
+ ///
public string? SessionId { get; set; }
///
@@ -786,6 +1154,9 @@ protected SessionConfig(SessionConfig? other)
///
public string? ClientName { get; set; }
+ ///
+ /// Model identifier to use for this session (e.g., "gpt-4o").
+ ///
public string? Model { get; set; }
///
@@ -801,11 +1172,25 @@ protected SessionConfig(SessionConfig? other)
///
public string? ConfigDir { get; set; }
+ ///
+ /// Custom tool functions available to the language model during the session.
+ ///
public ICollection? Tools { get; set; }
-
+ ///
+ /// 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.
+ ///
public List? AvailableTools { get; set; }
+ ///
+ /// List of tool names to exclude from the session.
+ ///
public List? ExcludedTools { get; set; }
+ ///
+ /// Custom model provider configuration for the session.
+ ///
public ProviderConfig? Provider { get; set; }
///
@@ -874,9 +1259,15 @@ 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() => new(this);
+ public virtual SessionConfig Clone()
+ {
+ return new(this);
+ }
}
+///
+/// Configuration options for resuming an existing Copilot session.
+///
public class ResumeSessionConfig
{
///
@@ -927,6 +1318,9 @@ protected ResumeSessionConfig(ResumeSessionConfig? other)
///
public string? Model { get; set; }
+ ///
+ /// Custom tool functions available to the language model during the resumed session.
+ ///
public ICollection? Tools { get; set; }
///
@@ -946,6 +1340,9 @@ protected ResumeSessionConfig(ResumeSessionConfig? other)
///
public List? ExcludedTools { get; set; }
+ ///
+ /// Custom model provider configuration for the resumed session.
+ ///
public ProviderConfig? Provider { get; set; }
///
@@ -1030,9 +1427,15 @@ 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() => new(this);
+ public virtual ResumeSessionConfig Clone()
+ {
+ return new(this);
+ }
}
+///
+/// Options for sending a message in a Copilot session.
+///
public class MessageOptions
{
///
@@ -1053,8 +1456,17 @@ protected MessageOptions(MessageOptions? other)
Prompt = other.Prompt;
}
+ ///
+ /// The prompt text to send to the assistant.
+ ///
public string Prompt { get; set; } = string.Empty;
+ ///
+ /// File or data attachments to include with the message.
+ ///
public List? Attachments { get; set; }
+ ///
+ /// Interaction mode for the message (e.g., "plan", "edit").
+ ///
public string? Mode { get; set; }
///
@@ -1066,9 +1478,15 @@ 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() => new(this);
+ public virtual MessageOptions Clone()
+ {
+ return new(this);
+ }
}
+///
+/// Delegate for handling session events emitted during a Copilot session.
+///
public delegate void SessionEventHandler(SessionEvent sessionEvent);
///
@@ -1101,12 +1519,30 @@ public class SessionListFilter
public string? Branch { get; set; }
}
+///
+/// Metadata describing a Copilot session.
+///
public class SessionMetadata
{
+ ///
+ /// Unique identifier of the session.
+ ///
public string SessionId { get; set; } = string.Empty;
+ ///
+ /// Time when the session was created.
+ ///
public DateTime StartTime { get; set; }
+ ///
+ /// Time when the session was last modified.
+ ///
public DateTime ModifiedTime { get; set; }
+ ///
+ /// Human-readable summary of the session.
+ ///
public string? Summary { get; set; }
+ ///
+ /// Whether the session is running on a remote server.
+ ///
public bool IsRemote { get; set; }
/// Working directory context (cwd, git info) from session creation.
public SessionContext? Context { get; set; }
@@ -1117,10 +1553,22 @@ internal class PingRequest
public string? Message { get; set; }
}
+///
+/// Response from a server ping request.
+///
public class PingResponse
{
+ ///
+ /// Echo of the ping message.
+ ///
public string Message { get; set; } = string.Empty;
+ ///
+ /// Server timestamp when the ping was processed.
+ ///
public long Timestamp { get; set; }
+ ///
+ /// Protocol version supported by the server.
+ ///
public int? ProtocolVersion { get; set; }
}
@@ -1147,7 +1595,17 @@ public class GetAuthStatusResponse
[JsonPropertyName("isAuthenticated")]
public bool IsAuthenticated { get; set; }
- /// Authentication type (user, env, gh-cli, hmac, api-key, token)
+ ///
+ /// Authentication type.
+ ///
+ /// - "user" — authenticated via user login.
+ /// - "env" — authenticated via environment variable.
+ /// - "gh-cli" — authenticated via the GitHub CLI.
+ /// - "hmac" — authenticated via HMAC signature.
+ /// - "api-key" — authenticated via API key.
+ /// - "token" — authenticated via explicit token.
+ ///
+ ///
[JsonPropertyName("authType")]
public string? AuthType { get; set; }
@@ -1169,12 +1627,21 @@ public class GetAuthStatusResponse
///
public class ModelVisionLimits
{
+ ///
+ /// List of supported image MIME types (e.g., "image/png", "image/jpeg").
+ ///
[JsonPropertyName("supported_media_types")]
- public List SupportedMediaTypes { get; set; } = new();
+ public List SupportedMediaTypes { get; set; } = [];
+ ///
+ /// Maximum number of images allowed in a single prompt.
+ ///
[JsonPropertyName("max_prompt_images")]
public int MaxPromptImages { get; set; }
+ ///
+ /// Maximum size in bytes for a single prompt image.
+ ///
[JsonPropertyName("max_prompt_image_size")]
public int MaxPromptImageSize { get; set; }
}
@@ -1184,12 +1651,21 @@ public class ModelVisionLimits
///
public class ModelLimits
{
+ ///
+ /// Maximum number of tokens allowed in the prompt.
+ ///
[JsonPropertyName("max_prompt_tokens")]
public int? MaxPromptTokens { get; set; }
+ ///
+ /// Maximum total tokens in the context window.
+ ///
[JsonPropertyName("max_context_window_tokens")]
public int MaxContextWindowTokens { get; set; }
+ ///
+ /// Vision-specific limits for the model.
+ ///
[JsonPropertyName("vision")]
public ModelVisionLimits? Vision { get; set; }
}
@@ -1199,6 +1675,9 @@ public class ModelLimits
///
public class ModelSupports
{
+ ///
+ /// Whether this model supports image/vision inputs.
+ ///
[JsonPropertyName("vision")]
public bool Vision { get; set; }
@@ -1214,9 +1693,15 @@ public class ModelSupports
///
public class ModelCapabilities
{
+ ///
+ /// Feature support flags for the model.
+ ///
[JsonPropertyName("supports")]
public ModelSupports Supports { get; set; } = new();
+ ///
+ /// Token and resource limits for the model.
+ ///
[JsonPropertyName("limits")]
public ModelLimits Limits { get; set; } = new();
}
@@ -1226,9 +1711,15 @@ public class ModelCapabilities
///
public class ModelPolicy
{
+ ///
+ /// Policy state of the model (e.g., "enabled", "disabled").
+ ///
[JsonPropertyName("state")]
public string State { get; set; } = string.Empty;
+ ///
+ /// Terms or conditions associated with using the model.
+ ///
[JsonPropertyName("terms")]
public string Terms { get; set; } = string.Empty;
}
@@ -1238,6 +1729,9 @@ public class ModelPolicy
///
public class ModelBilling
{
+ ///
+ /// Billing cost multiplier relative to the base model rate.
+ ///
[JsonPropertyName("multiplier")]
public double Multiplier { get; set; }
}
@@ -1281,8 +1775,11 @@ public class ModelInfo
///
public class GetModelsResponse
{
+ ///
+ /// List of available models.
+ ///
[JsonPropertyName("models")]
- public List Models { get; set; } = new();
+ public List Models { get; set; } = [];
}
// ============================================================================
@@ -1294,10 +1791,15 @@ public class GetModelsResponse
///
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";
}
@@ -1306,12 +1808,21 @@ public static class SessionLifecycleEventTypes
///
public class SessionLifecycleEventMetadata
{
+ ///
+ /// ISO 8601 timestamp when the session was created.
+ ///
[JsonPropertyName("startTime")]
public string StartTime { get; set; } = string.Empty;
+ ///
+ /// ISO 8601 timestamp when the session was last modified.
+ ///
[JsonPropertyName("modifiedTime")]
public string ModifiedTime { get; set; } = string.Empty;
+ ///
+ /// Human-readable summary of the session.
+ ///
[JsonPropertyName("summary")]
public string? Summary { get; set; }
}
@@ -1321,12 +1832,21 @@ public class SessionLifecycleEventMetadata
///
public class SessionLifecycleEvent
{
+ ///
+ /// Type of lifecycle event (see ).
+ ///
[JsonPropertyName("type")]
public string Type { get; set; } = string.Empty;
+ ///
+ /// Identifier of the session this event pertains to.
+ ///
[JsonPropertyName("sessionId")]
public string SessionId { get; set; } = string.Empty;
+ ///
+ /// Metadata associated with the session lifecycle event.
+ ///
[JsonPropertyName("metadata")]
public SessionLifecycleEventMetadata? Metadata { get; set; }
}
@@ -1336,9 +1856,15 @@ public class SessionLifecycleEvent
///
public class GetForegroundSessionResponse
{
+ ///
+ /// Identifier of the current foreground session, or null if none.
+ ///
[JsonPropertyName("sessionId")]
public string? SessionId { get; set; }
+ ///
+ /// Workspace path associated with the foreground session.
+ ///
[JsonPropertyName("workspacePath")]
public string? WorkspacePath { get; set; }
}
@@ -1348,9 +1874,15 @@ public class GetForegroundSessionResponse
///
public class SetForegroundSessionResponse
{
+ ///
+ /// Whether the foreground session was set successfully.
+ ///
[JsonPropertyName("success")]
public bool Success { get; set; }
+ ///
+ /// Error message if the operation failed.
+ ///
[JsonPropertyName("error")]
public string? Error { get; set; }
}
diff --git a/dotnet/test/ClientTests.cs b/dotnet/test/ClientTests.cs
index 91b7f9241..3c3f3bdaa 100644
--- a/dotnet/test/ClientTests.cs
+++ b/dotnet/test/ClientTests.cs
@@ -230,14 +230,11 @@ public async Task Should_Report_Error_With_Stderr_When_CLI_Fails_To_Start()
{
var client = new CopilotClient(new CopilotClientOptions
{
- CliArgs = new[] { "--nonexistent-flag-for-testing" },
+ CliArgs = ["--nonexistent-flag-for-testing"],
UseStdio = true
});
- var ex = await Assert.ThrowsAsync(async () =>
- {
- await client.StartAsync();
- });
+ var ex = await Assert.ThrowsAsync(() => client.StartAsync());
var errorMessage = ex.Message;
// Verify we get the stderr output in the error message
@@ -261,10 +258,7 @@ public async Task Should_Throw_When_CreateSession_Called_Without_PermissionHandl
{
using var client = new CopilotClient(new CopilotClientOptions());
- var ex = await Assert.ThrowsAsync(async () =>
- {
- await client.CreateSessionAsync(new SessionConfig());
- });
+ var ex = await Assert.ThrowsAsync(() => client.CreateSessionAsync(new SessionConfig()));
Assert.Contains("OnPermissionRequest", ex.Message);
Assert.Contains("is required", ex.Message);
@@ -275,10 +269,7 @@ public async Task Should_Throw_When_ResumeSession_Called_Without_PermissionHandl
{
using var client = new CopilotClient(new CopilotClientOptions());
- var ex = await Assert.ThrowsAsync(async () =>
- {
- await client.ResumeSessionAsync("some-session-id", new ResumeSessionConfig());
- });
+ var ex = await Assert.ThrowsAsync(() => client.ResumeSessionAsync("some-session-id", new()));
Assert.Contains("OnPermissionRequest", ex.Message);
Assert.Contains("is required", ex.Message);
diff --git a/dotnet/test/GitHub.Copilot.SDK.Test.csproj b/dotnet/test/GitHub.Copilot.SDK.Test.csproj
index 654a988a0..fbc9f17c3 100644
--- a/dotnet/test/GitHub.Copilot.SDK.Test.csproj
+++ b/dotnet/test/GitHub.Copilot.SDK.Test.csproj
@@ -1,10 +1,6 @@
- net8.0
- enable
- enable
- true
false
@@ -19,17 +15,17 @@
-
-
-
+
+
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
-
+
diff --git a/dotnet/test/Harness/CapiProxy.cs b/dotnet/test/Harness/CapiProxy.cs
index a03502979..e6208f251 100644
--- a/dotnet/test/Harness/CapiProxy.cs
+++ b/dotnet/test/Harness/CapiProxy.cs
@@ -12,7 +12,7 @@
namespace GitHub.Copilot.SDK.Test.Harness;
-public partial class CapiProxy : IAsyncDisposable
+public sealed partial class CapiProxy : IAsyncDisposable
{
private Process? _process;
private Task? _startupTask;
@@ -129,10 +129,13 @@ public async Task> GetExchangesAsync()
using var client = new HttpClient();
return await client.GetFromJsonAsync($"{url}/exchanges", CapiProxyJsonContext.Default.ListParsedHttpExchange)
- ?? new List();
+ ?? [];
}
- public async ValueTask DisposeAsync() => await StopAsync();
+ public async ValueTask DisposeAsync()
+ {
+ await StopAsync();
+ }
private static string FindRepoRoot()
{
diff --git a/dotnet/test/Harness/E2ETestBase.cs b/dotnet/test/Harness/E2ETestBase.cs
index dc1fa465d..e982090cb 100644
--- a/dotnet/test/Harness/E2ETestBase.cs
+++ b/dotnet/test/Harness/E2ETestBase.cs
@@ -40,7 +40,10 @@ public async Task InitializeAsync()
await Ctx.ConfigureForTestAsync(_snapshotCategory, _testName);
}
- public Task DisposeAsync() => Task.CompletedTask;
+ public Task DisposeAsync()
+ {
+ return Task.CompletedTask;
+ }
///
/// Creates a session with a default config that approves all permissions.
@@ -64,9 +67,13 @@ protected Task ResumeSessionAsync(string sessionId, ResumeSessio
return Client.ResumeSessionAsync(sessionId, config);
}
- protected static string GetSystemMessage(ParsedHttpExchange exchange) =>
- exchange.Request.Messages.FirstOrDefault(m => m.Role == "system")?.Content ?? string.Empty;
+ protected static string GetSystemMessage(ParsedHttpExchange exchange)
+ {
+ return exchange.Request.Messages.FirstOrDefault(m => m.Role == "system")?.Content ?? string.Empty;
+ }
- protected static List GetToolNames(ParsedHttpExchange exchange) =>
- exchange.Request.Tools?.Select(t => t.Function.Name).ToList() ?? new();
+ protected static List GetToolNames(ParsedHttpExchange exchange)
+ {
+ return exchange.Request.Tools?.Select(t => t.Function.Name).ToList() ?? [];
+ }
}
diff --git a/dotnet/test/Harness/E2ETestContext.cs b/dotnet/test/Harness/E2ETestContext.cs
index 00fc32075..a4472e1d3 100644
--- a/dotnet/test/Harness/E2ETestContext.cs
+++ b/dotnet/test/Harness/E2ETestContext.cs
@@ -7,7 +7,7 @@
namespace GitHub.Copilot.SDK.Test.Harness;
-public class E2ETestContext : IAsyncDisposable
+public sealed class E2ETestContext : IAsyncDisposable
{
public string HomeDir { get; }
public string WorkDir { get; }
@@ -74,7 +74,10 @@ public async Task ConfigureForTestAsync(string testFile, [CallerMemberName] stri
await _proxy.ConfigureAsync(snapshotPath, WorkDir);
}
- public Task> GetExchangesAsync() => _proxy.GetExchangesAsync();
+ public Task> GetExchangesAsync()
+ {
+ return _proxy.GetExchangesAsync();
+ }
public IReadOnlyDictionary GetEnvironment()
{
@@ -89,13 +92,16 @@ public IReadOnlyDictionary GetEnvironment()
return env!;
}
- public CopilotClient CreateClient() => new(new CopilotClientOptions
+ public CopilotClient CreateClient()
{
- Cwd = WorkDir,
- CliPath = GetCliPath(_repoRoot),
- Environment = GetEnvironment(),
- GitHubToken = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("CI")) ? "fake-token-for-e2e-tests" : null,
- });
+ return new(new CopilotClientOptions
+ {
+ Cwd = WorkDir,
+ CliPath = GetCliPath(_repoRoot),
+ Environment = GetEnvironment(),
+ GitHubToken = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("CI")) ? "fake-token-for-e2e-tests" : null,
+ });
+ }
public async ValueTask DisposeAsync()
{
diff --git a/dotnet/test/SessionTests.cs b/dotnet/test/SessionTests.cs
index eac00b06e..442cbfb0d 100644
--- a/dotnet/test/SessionTests.cs
+++ b/dotnet/test/SessionTests.cs
@@ -95,7 +95,7 @@ public async Task Should_Create_A_Session_With_AvailableTools()
{
var session = await CreateSessionAsync(new SessionConfig
{
- AvailableTools = new List { "view", "edit" }
+ AvailableTools = ["view", "edit"]
});
await session.SendAsync(new MessageOptions { Prompt = "What is 1+1?" });
@@ -115,7 +115,7 @@ public async Task Should_Create_A_Session_With_ExcludedTools()
{
var session = await CreateSessionAsync(new SessionConfig
{
- ExcludedTools = new List { "view" }
+ ExcludedTools = ["view"]
});
await session.SendAsync(new MessageOptions { Prompt = "What is 1+1?" });
diff --git a/dotnet/test/ToolsTests.cs b/dotnet/test/ToolsTests.cs
index 1bb6b2cb2..469645ba0 100644
--- a/dotnet/test/ToolsTests.cs
+++ b/dotnet/test/ToolsTests.cs
@@ -137,7 +137,7 @@ await session.SendAsync(new MessageOptions
City[] PerformDbQuery(DbQueryOptions query, AIFunctionArguments rawArgs)
{
Assert.Equal("cities", query.Table);
- Assert.Equal(new[] { 12, 19 }, query.Ids);
+ Assert.Equal([12, 19], query.Ids);
Assert.True(query.SortAscending);
receivedInvocation = (ToolInvocation)rawArgs.Context![typeof(ToolInvocation)]!;
return [new(19, "Passos", 135460), new(12, "San Lorenzo", 204356)];
@@ -200,7 +200,7 @@ await session.SendAsync(new MessageOptions
Assert.Contains("yellow", assistantMessage!.Data.Content?.ToLowerInvariant() ?? string.Empty);
- static ToolResultAIContent GetImage() => new ToolResultAIContent(new()
+ static ToolResultAIContent GetImage() => new(new()
{
BinaryResultsForLlm = [new() {
// 2x2 yellow square
@@ -256,10 +256,7 @@ public async Task Denies_Custom_Tool_When_Permission_Denied()
var session = await Client.CreateSessionAsync(new SessionConfig
{
Tools = [AIFunctionFactory.Create(EncryptStringDenied, "encrypt_string")],
- OnPermissionRequest = (request, invocation) =>
- {
- return Task.FromResult(new PermissionRequestResult { Kind = "denied-interactively-by-user" });
- },
+ OnPermissionRequest = async (request, invocation) => new() { Kind = "denied-interactively-by-user" },
});
await session.SendAsync(new MessageOptions
diff --git a/nodejs/scripts/update-protocol-version.ts b/nodejs/scripts/update-protocol-version.ts
index d0e3ecc66..46f6189e8 100644
--- a/nodejs/scripts/update-protocol-version.ts
+++ b/nodejs/scripts/update-protocol-version.ts
@@ -106,7 +106,7 @@ internal static class SdkProtocolVersion
///
/// The SDK protocol version.
///
- public const int Version = ${version};
+ private const int Version = ${version};
///
/// Gets the SDK protocol version.
diff --git a/nodejs/test/client.test.ts b/nodejs/test/client.test.ts
index f9618b6dd..2fa8eb434 100644
--- a/nodejs/test/client.test.ts
+++ b/nodejs/test/client.test.ts
@@ -68,7 +68,13 @@ describe("CopilotClient", () => {
onTestFinished(() => client.forceStop());
const session = await client.createSession({ onPermissionRequest: approveAll });
- const spy = vi.spyOn((client as any).connection!, "sendRequest");
+ // Mock sendRequest to capture the call without hitting the runtime
+ const spy = vi
+ .spyOn((client as any).connection!, "sendRequest")
+ .mockImplementation(async (method: string, params: any) => {
+ if (method === "session.resume") return { sessionId: params.sessionId };
+ throw new Error(`Unexpected method: ${method}`);
+ });
await client.resumeSession(session.sessionId, {
clientName: "my-app",
onPermissionRequest: approveAll,
@@ -78,6 +84,7 @@ describe("CopilotClient", () => {
"session.resume",
expect.objectContaining({ clientName: "my-app", sessionId: session.sessionId })
);
+ spy.mockRestore();
});
it("sends session.model.switchTo RPC with correct params", async () => {
@@ -325,7 +332,13 @@ describe("CopilotClient", () => {
onTestFinished(() => client.forceStop());
const session = await client.createSession({ onPermissionRequest: approveAll });
- const spy = vi.spyOn((client as any).connection!, "sendRequest");
+ // Mock sendRequest to capture the call without hitting the runtime
+ const spy = vi
+ .spyOn((client as any).connection!, "sendRequest")
+ .mockImplementation(async (method: string, params: any) => {
+ if (method === "session.resume") return { sessionId: params.sessionId };
+ throw new Error(`Unexpected method: ${method}`);
+ });
await client.resumeSession(session.sessionId, {
onPermissionRequest: approveAll,
tools: [
@@ -342,6 +355,7 @@ describe("CopilotClient", () => {
expect(payload.tools).toEqual([
expect.objectContaining({ name: "grep", overridesBuiltInTool: true }),
]);
+ spy.mockRestore();
});
});
});
diff --git a/scripts/codegen/csharp.ts b/scripts/codegen/csharp.ts
index f2e536257..a759c1135 100644
--- a/scripts/codegen/csharp.ts
+++ b/scripts/codegen/csharp.ts
@@ -393,6 +393,9 @@ function generateSessionEventsCode(schema: JSONSchema7): string {
// AUTO-GENERATED FILE - DO NOT EDIT
// Generated from: session-events.schema.json
+// Generated code does not have XML doc comments; suppress CS1591 to avoid warnings.
+#pragma warning disable CS1591
+
using System.Text.Json;
using System.Text.Json.Serialization;
@@ -532,7 +535,8 @@ function emitRpcClass(className: string, schema: JSONSchema7, visibility: "publi
let defaultVal = "";
if (isReq && !csharpType.endsWith("?")) {
if (csharpType === "string") defaultVal = " = string.Empty;";
- else if (csharpType.startsWith("List<") || csharpType.startsWith("Dictionary<") || emittedRpcClasses.has(csharpType)) defaultVal = " = new();";
+ else if (csharpType.startsWith("List<") || csharpType.startsWith("Dictionary<")) defaultVal = " = [];";
+ else if (emittedRpcClasses.has(csharpType)) defaultVal = " = new();";
}
lines.push(` public ${csharpType} ${csharpName} { get; set; }${defaultVal}`);
if (i < props.length - 1) lines.push("");
@@ -738,6 +742,9 @@ function generateRpcCode(schema: ApiSchema): string {
// AUTO-GENERATED FILE - DO NOT EDIT
// Generated from: api.schema.json
+// Generated code does not have XML doc comments; suppress CS1591 to avoid warnings.
+#pragma warning disable CS1591
+
using System.Text.Json;
using System.Text.Json.Serialization;
using StreamJsonRpc;