diff --git a/dotnet/src/Client.cs b/dotnet/src/Client.cs
index 76af9b3a..8cad6b04 100644
--- a/dotnet/src/Client.cs
+++ b/dotnet/src/Client.cs
@@ -54,6 +54,11 @@ namespace GitHub.Copilot.SDK;
///
public sealed partial class CopilotClient : IDisposable, IAsyncDisposable
{
+ ///
+ /// Minimum protocol version this SDK can communicate with.
+ ///
+ private const int MinProtocolVersion = 2;
+
private readonly ConcurrentDictionary _sessions = new();
private readonly CopilotClientOptions _options;
private readonly ILogger _logger;
@@ -62,6 +67,7 @@ public sealed partial class CopilotClient : IDisposable, IAsyncDisposable
private readonly int? _optionsPort;
private readonly string? _optionsHost;
private int? _actualPort;
+ private int? _negotiatedProtocolVersion;
private List? _modelsCache;
private readonly SemaphoreSlim _modelsCacheLock = new(1, 1);
private readonly List> _lifecycleHandlers = [];
@@ -923,27 +929,30 @@ private Task EnsureConnectedAsync(CancellationToken cancellationToke
return (Task)StartAsync(cancellationToken);
}
- private static async Task VerifyProtocolVersionAsync(Connection connection, CancellationToken cancellationToken)
+ private async Task VerifyProtocolVersionAsync(Connection connection, CancellationToken cancellationToken)
{
- var expectedVersion = SdkProtocolVersion.GetVersion();
+ var maxVersion = SdkProtocolVersion.GetVersion();
var pingResponse = await InvokeRpcAsync(
connection.Rpc, "ping", [new PingRequest()], connection.StderrBuffer, cancellationToken);
if (!pingResponse.ProtocolVersion.HasValue)
{
throw new InvalidOperationException(
- $"SDK protocol version mismatch: SDK expects version {expectedVersion}, " +
+ $"SDK protocol version mismatch: SDK supports versions {MinProtocolVersion}-{maxVersion}, " +
$"but server does not report a protocol version. " +
$"Please update your server to ensure compatibility.");
}
- if (pingResponse.ProtocolVersion.Value != expectedVersion)
+ var serverVersion = pingResponse.ProtocolVersion.Value;
+ if (serverVersion < MinProtocolVersion || serverVersion > maxVersion)
{
throw new InvalidOperationException(
- $"SDK protocol version mismatch: SDK expects version {expectedVersion}, " +
- $"but server reports version {pingResponse.ProtocolVersion.Value}. " +
+ $"SDK protocol version mismatch: SDK supports versions {MinProtocolVersion}-{maxVersion}, " +
+ $"but server reports version {serverVersion}. " +
$"Please update your SDK or server to ensure compatibility.");
}
+
+ _negotiatedProtocolVersion = serverVersion;
}
private static async Task<(Process Process, int? DetectedLocalhostTcpPort, StringBuilder StderrBuffer)> StartCliServerAsync(CopilotClientOptions options, ILogger logger, CancellationToken cancellationToken)
@@ -1137,6 +1146,12 @@ private async Task ConnectToServerAsync(Process? cliProcess, string?
var handler = new RpcHandler(this);
rpc.AddLocalRpcMethod("session.event", handler.OnSessionEvent);
rpc.AddLocalRpcMethod("session.lifecycle", handler.OnSessionLifecycle);
+ // Protocol v3 servers send tool calls / permission requests as broadcast events.
+ // Protocol v2 servers use the older tool.call / permission.request RPC model.
+ // We always register v2 adapters because handlers are set up before version
+ // negotiation; a v3 server will simply never send these requests.
+ rpc.AddLocalRpcMethod("tool.call", handler.OnToolCallV2);
+ rpc.AddLocalRpcMethod("permission.request", handler.OnPermissionRequestV2);
rpc.AddLocalRpcMethod("userInput.request", handler.OnUserInputRequest);
rpc.AddLocalRpcMethod("hooks.invoke", handler.OnHooksInvoke);
rpc.StartListening();
@@ -1257,6 +1272,96 @@ public async Task OnHooksInvoke(string sessionId, string ho
var output = await session.HandleHooksInvokeAsync(hookType, input);
return new HooksInvokeResponse(output);
}
+
+ // Protocol v2 backward-compatibility adapters
+
+ public async Task OnToolCallV2(string sessionId,
+ string toolCallId,
+ string toolName,
+ object? arguments)
+ {
+ var session = client.GetSession(sessionId) ?? throw new ArgumentException($"Unknown session {sessionId}");
+ if (session.GetTool(toolName) is not { } tool)
+ {
+ return new ToolCallResponseV2(new ToolResultObject
+ {
+ TextResultForLlm = $"Tool '{toolName}' is not supported.",
+ ResultType = "failure",
+ Error = $"tool '{toolName}' not supported"
+ });
+ }
+
+ try
+ {
+ var invocation = new ToolInvocation
+ {
+ SessionId = sessionId,
+ ToolCallId = toolCallId,
+ ToolName = toolName,
+ Arguments = arguments
+ };
+
+ var aiFunctionArgs = new AIFunctionArguments
+ {
+ Context = new Dictionary