Skip to content

Commit 13f7116

Browse files
Drop protocol v2 support: bump MIN to 3 + delete v2 RPC handlers
Bumps the minimum supported wire protocol version from 2 to 3 across TypeScript, Python, .NET, Go, and Rust (Java intentionally left at 2 — out of scope for this PR). Before this change every SDK registered v2-only RPC handlers for ``tool.call`` and ``permission.request`` alongside the v3 broadcast-event model. The handlers were dead code on a v3 server (the server only ever sends broadcast events) but were kept "just in case" the SDK ever connected to a v2 server. Now that ``MIN_PROTOCOL_VERSION = 3``, a v2 server is rejected at the ``connect`` handshake before any session-level RPC can fire, so the v2 adapters can be deleted outright. Deletions per SDK: - **.NET** (``Client.cs``): - ``OnToolCallV2`` and ``OnPermissionRequestV2`` RPC handler methods - ``ToolCallResponseV2`` and ``PermissionRequestResponseV2`` records - Their ``[JsonSerializable]`` entries - ``NoResultPermissionDirectRpcErrorMessage`` constant (no longer reachable) - **TypeScript** (``client.ts``, ``session.ts``): - ``handleToolCallRequestV2`` / ``handlePermissionRequestV2`` private methods on ``CopilotClient`` - ``normalizeToolResultV2`` / ``isToolResultObject`` helpers - ``CopilotSession._handlePermissionRequestV2`` - ``NO_RESULT_PERMISSION_V2_ERROR`` constant - Unused ``ToolCallRequestPayload`` / ``ToolCallResponsePayload`` / ``ToolResultObject`` imports in ``client.ts`` - **Python** (``client.py``): - ``_handle_tool_call_request_v2`` and ``_handle_permission_request_v2`` methods (both create_session and resume_session paths) - The whole "Protocol v2 backward-compatibility adapters" section - ``_NO_RESULT_PERMISSION_V2_ERROR`` constant - ``test_v2_permission_adapter_rejects_no_result`` unit test - **Go** (``client.go``): - ``handleToolCallRequestV2`` and ``handlePermissionRequestV2`` methods - ``toolCallRequestV2`` / ``toolCallResponseV2`` / ``permissionRequestV2`` / ``permissionResponseV2`` payload types - ``noResultPermissionDirectRpcError`` constant - **Rust** (``session.rs``, ``lib.rs``): - ``permission.request`` direct-RPC match arm in the session router - ``direct_permission_payload`` helper + its unit tests What's preserved: - ``MIN_PROTOCOL_VERSION`` constant in each SDK (now ``= 3``) - The handshake check that rejects servers reporting older versions - ``negotiated_protocol_version`` field on the client (no longer branched on anywhere, but harmless and may grow back if we add a v4 later) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 8b51c98 commit 13f7116

8 files changed

Lines changed: 12 additions & 679 deletions

File tree

dotnet/src/Client.cs

Lines changed: 1 addition & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,10 @@ namespace GitHub.Copilot;
5353
/// </example>
5454
public sealed partial class CopilotClient : IDisposable, IAsyncDisposable
5555
{
56-
internal const string NoResultPermissionDirectRpcErrorMessage =
57-
"Permission handlers cannot return PermissionDecision.NoResult() for this permission request.";
58-
5956
/// <summary>
6057
/// Minimum protocol version this SDK can communicate with.
6158
/// </summary>
62-
private const int MinProtocolVersion = 2;
59+
private const int MinProtocolVersion = 3;
6360

6461
/// <summary>
6562
/// Provides a thread-safe collection of active Copilot sessions, indexed by session identifier.
@@ -1610,12 +1607,6 @@ private async Task<Connection> ConnectToServerAsync(Process? cliProcess, string?
16101607
var handler = new RpcHandler(this);
16111608
rpc.SetLocalRpcMethod("session.event", handler.OnSessionEvent);
16121609
rpc.SetLocalRpcMethod("session.lifecycle", handler.OnSessionLifecycle);
1613-
// Protocol v3 servers send tool calls / permission requests as broadcast events.
1614-
// Protocol v2 servers use the older tool.call / permission.request RPC model.
1615-
// We always register v2 adapters because handlers are set up before version
1616-
// negotiation; a v3 server will simply never send these requests.
1617-
rpc.SetLocalRpcMethod("tool.call", handler.OnToolCallV2);
1618-
rpc.SetLocalRpcMethod("permission.request", handler.OnPermissionRequestV2);
16191610
rpc.SetLocalRpcMethod("userInput.request", handler.OnUserInputRequest);
16201611
rpc.SetLocalRpcMethod("exitPlanMode.request", handler.OnExitPlanModeRequest);
16211612
rpc.SetLocalRpcMethod("autoModeSwitch.request", handler.OnAutoModeSwitchRequest);
@@ -1799,108 +1790,6 @@ public async ValueTask<SystemMessageTransformRpcResponse> OnSystemMessageTransfo
17991790
var session = client.GetSession(sessionId) ?? throw new ArgumentException($"Unknown session {sessionId}");
18001791
return await session.HandleSystemMessageTransformAsync(sections);
18011792
}
1802-
1803-
// Protocol v2 backward-compatibility adapters
1804-
1805-
public async ValueTask<ToolCallResponseV2> OnToolCallV2(string sessionId,
1806-
string toolCallId,
1807-
string toolName,
1808-
object? arguments,
1809-
string? traceparent = null,
1810-
string? tracestate = null)
1811-
{
1812-
using var _ = TelemetryHelpers.RestoreTraceContext(traceparent, tracestate);
1813-
1814-
var session = client.GetSession(sessionId) ?? throw new ArgumentException($"Unknown session {sessionId}");
1815-
if (session.GetTool(toolName) is not { } tool)
1816-
{
1817-
// Support for not providing the tool handler is only available in the v3+ model.
1818-
// For v2, it must have been provided.
1819-
return new ToolCallResponseV2(new ToolResultObject
1820-
{
1821-
TextResultForLlm = $"Tool '{toolName}' is not supported.",
1822-
ResultType = "failure",
1823-
Error = $"tool '{toolName}' not supported"
1824-
});
1825-
}
1826-
1827-
try
1828-
{
1829-
var invocation = new ToolInvocation
1830-
{
1831-
SessionId = sessionId,
1832-
ToolCallId = toolCallId,
1833-
ToolName = toolName,
1834-
Arguments = arguments
1835-
};
1836-
1837-
var aiFunctionArgs = new AIFunctionArguments
1838-
{
1839-
Context = new Dictionary<object, object?>
1840-
{
1841-
[typeof(ToolInvocation)] = invocation
1842-
}
1843-
};
1844-
1845-
if (arguments is not null)
1846-
{
1847-
if (arguments is not JsonElement incomingJsonArgs)
1848-
{
1849-
throw new InvalidOperationException($"Incoming arguments must be a {nameof(JsonElement)}; received {arguments.GetType().Name}");
1850-
}
1851-
1852-
foreach (var prop in incomingJsonArgs.EnumerateObject())
1853-
{
1854-
aiFunctionArgs[prop.Name] = prop.Value;
1855-
}
1856-
}
1857-
1858-
var toolTimestamp = Stopwatch.GetTimestamp();
1859-
var result = await tool.InvokeAsync(aiFunctionArgs);
1860-
LoggingHelpers.LogTiming(client._logger, LogLevel.Debug, null,
1861-
"RpcHandler.OnToolCallV2 tool dispatch. Elapsed={Elapsed}, SessionId={SessionId}, ToolCallId={ToolCallId}, Tool={ToolName}",
1862-
toolTimestamp,
1863-
sessionId,
1864-
toolCallId,
1865-
toolName);
1866-
1867-
var toolResultObject = ToolResultObject.ConvertFromInvocationResult(result, tool.JsonSerializerOptions);
1868-
return new ToolCallResponseV2(toolResultObject);
1869-
}
1870-
catch (Exception ex)
1871-
{
1872-
return new ToolCallResponseV2(new ToolResultObject
1873-
{
1874-
TextResultForLlm = "Invoking this tool produced an error. Detailed information is not available.",
1875-
ResultType = "failure",
1876-
Error = ex.Message
1877-
});
1878-
}
1879-
}
1880-
1881-
public async ValueTask<PermissionRequestResponseV2> OnPermissionRequestV2(string sessionId, JsonElement permissionRequest)
1882-
{
1883-
var session = client.GetSession(sessionId)
1884-
?? throw new ArgumentException($"Unknown session {sessionId}");
1885-
1886-
try
1887-
{
1888-
var decision = await session.HandlePermissionRequestAsync(permissionRequest);
1889-
if (decision is PermissionDecisionNoResult)
1890-
{
1891-
throw new InvalidOperationException(NoResultPermissionDirectRpcErrorMessage);
1892-
}
1893-
return new PermissionRequestResponseV2(decision);
1894-
}
1895-
catch (InvalidOperationException ex) when (ex.Message == NoResultPermissionDirectRpcErrorMessage)
1896-
{
1897-
throw;
1898-
}
1899-
catch (Exception)
1900-
{
1901-
return new PermissionRequestResponseV2(PermissionDecision.UserNotAvailable());
1902-
}
1903-
}
19041793
}
19051794

19061795
private class Connection(
@@ -2071,13 +1960,6 @@ internal record AutoModeSwitchRequestResponse(
20711960
internal record HooksInvokeResponse(
20721961
object? Output);
20731962

2074-
// Protocol v2 backward-compatibility response types
2075-
internal record ToolCallResponseV2(
2076-
ToolResultObject Result);
2077-
2078-
internal record PermissionRequestResponseV2(
2079-
PermissionDecision Result);
2080-
20811963
[JsonSourceGenerationOptions(
20821964
JsonSerializerDefaults.Web,
20831965
AllowOutOfOrderMetadataProperties = true,
@@ -2100,7 +1982,6 @@ internal record PermissionRequestResponseV2(
21001982
[JsonSerializable(typeof(GetSessionMetadataRequest))]
21011983
[JsonSerializable(typeof(GetSessionMetadataResponse))]
21021984
[JsonSerializable(typeof(ModelCapabilitiesOverride))]
2103-
[JsonSerializable(typeof(PermissionRequestResponseV2))]
21041985
[JsonSerializable(typeof(ProviderConfig))]
21051986
[JsonSerializable(typeof(ResumeSessionRequest))]
21061987
[JsonSerializable(typeof(ResumeSessionResponse))]
@@ -2111,7 +1992,6 @@ internal record PermissionRequestResponseV2(
21111992
[JsonSerializable(typeof(SystemMessageConfig))]
21121993
[JsonSerializable(typeof(SystemMessageTransformRpcResponse))]
21131994
[JsonSerializable(typeof(CommandWireDefinition))]
2114-
[JsonSerializable(typeof(ToolCallResponseV2))]
21151995
[JsonSerializable(typeof(ToolDefinition))]
21161996
[JsonSerializable(typeof(ToolResultAIContent))]
21171997
[JsonSerializable(typeof(ToolResultObject))]

go/client.go

Lines changed: 1 addition & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,6 @@ import (
5252
"github.com/github/copilot-sdk/go/rpc"
5353
)
5454

55-
const noResultPermissionDirectRpcError = "permission handlers cannot return PermissionDecisionNoResult for this permission request"
56-
5755
func validateSessionFsConfig(config *SessionFsConfig) error {
5856
if config == nil {
5957
return nil
@@ -1384,7 +1382,7 @@ func (c *Client) ListModels(ctx context.Context) ([]ModelInfo, error) {
13841382
}
13851383

13861384
// minProtocolVersion is the minimum protocol version this SDK can communicate with.
1387-
const minProtocolVersion = 2
1385+
const minProtocolVersion = 3
13881386

13891387
// verifyProtocolVersion sends the `connect` handshake (carrying the optional token) and
13901388
// verifies the server's protocol version. Falls back to `ping` against legacy servers
@@ -1740,15 +1738,9 @@ func (c *Client) connectViaTcp(ctx context.Context) error {
17401738
}
17411739

17421740
// setupNotificationHandler configures handlers for session events and RPC requests.
1743-
// Protocol v3 servers send tool calls and permission requests as broadcast session events.
1744-
// Protocol v2 servers use the older tool.call / permission.request RPC model.
1745-
// We always register v2 adapters because handlers are set up before version negotiation;
1746-
// a v3 server will simply never send these requests.
17471741
func (c *Client) setupNotificationHandler() {
17481742
c.client.SetRequestHandler("session.event", jsonrpc2.NotificationHandlerFor(c.handleSessionEvent))
17491743
c.client.SetRequestHandler("session.lifecycle", jsonrpc2.NotificationHandlerFor(c.handleLifecycleEvent))
1750-
c.client.SetRequestHandler("tool.call", jsonrpc2.RequestHandlerFor(c.handleToolCallRequestV2))
1751-
c.client.SetRequestHandler("permission.request", jsonrpc2.RequestHandlerFor(c.handlePermissionRequestV2))
17521744
c.client.SetRequestHandler("userInput.request", jsonrpc2.RequestHandlerFor(c.handleUserInputRequest))
17531745
c.client.SetRequestHandler("exitPlanMode.request", jsonrpc2.RequestHandlerFor(c.handleExitPlanModeRequest))
17541746
c.client.SetRequestHandler("autoModeSwitch.request", jsonrpc2.RequestHandlerFor(c.handleAutoModeSwitchRequest))
@@ -1902,115 +1894,3 @@ func (c *Client) handleSystemMessageTransform(req systemMessageTransformRequest)
19021894
}
19031895
return resp, nil
19041896
}
1905-
1906-
// ========================================================================
1907-
// Protocol v2 backward-compatibility adapters
1908-
// ========================================================================
1909-
1910-
// toolCallRequestV2 is the v2 RPC request payload for tool.call.
1911-
type toolCallRequestV2 struct {
1912-
SessionID string `json:"sessionId"`
1913-
ToolCallID string `json:"toolCallId"`
1914-
ToolName string `json:"toolName"`
1915-
Arguments any `json:"arguments"`
1916-
Traceparent string `json:"traceparent,omitempty"`
1917-
Tracestate string `json:"tracestate,omitempty"`
1918-
}
1919-
1920-
// toolCallResponseV2 is the v2 RPC response payload for tool.call.
1921-
type toolCallResponseV2 struct {
1922-
Result ToolResult `json:"result"`
1923-
}
1924-
1925-
// permissionRequestV2 is the v2 RPC request payload for permission.request.
1926-
type permissionRequestV2 struct {
1927-
SessionID string `json:"sessionId"`
1928-
Request PermissionRequest `json:"permissionRequest"`
1929-
}
1930-
1931-
// permissionResponseV2 is the v2 RPC response payload for permission.request.
1932-
type permissionResponseV2 struct {
1933-
Result rpc.PermissionDecision `json:"result"`
1934-
}
1935-
1936-
// handleToolCallRequestV2 handles a v2-style tool.call RPC request from the server.
1937-
func (c *Client) handleToolCallRequestV2(req toolCallRequestV2) (*toolCallResponseV2, *jsonrpc2.Error) {
1938-
if req.SessionID == "" || req.ToolCallID == "" || req.ToolName == "" {
1939-
return nil, &jsonrpc2.Error{Code: -32602, Message: "invalid tool call payload"}
1940-
}
1941-
1942-
c.sessionsMux.Lock()
1943-
session, ok := c.sessions[req.SessionID]
1944-
c.sessionsMux.Unlock()
1945-
if !ok {
1946-
return nil, &jsonrpc2.Error{Code: -32602, Message: fmt.Sprintf("unknown session %s", req.SessionID)}
1947-
}
1948-
1949-
handler, ok := session.getToolHandler(req.ToolName)
1950-
if !ok {
1951-
return &toolCallResponseV2{Result: ToolResult{
1952-
TextResultForLLM: fmt.Sprintf("Tool '%s' is not supported by this client instance.", req.ToolName),
1953-
ResultType: "failure",
1954-
Error: fmt.Sprintf("tool '%s' not supported", req.ToolName),
1955-
ToolTelemetry: map[string]any{},
1956-
}}, nil
1957-
}
1958-
1959-
ctx := contextWithTraceParent(context.Background(), req.Traceparent, req.Tracestate)
1960-
1961-
invocation := ToolInvocation{
1962-
SessionID: req.SessionID,
1963-
ToolCallID: req.ToolCallID,
1964-
ToolName: req.ToolName,
1965-
Arguments: req.Arguments,
1966-
TraceContext: ctx,
1967-
}
1968-
1969-
result, err := handler(invocation)
1970-
if err != nil {
1971-
return &toolCallResponseV2{Result: ToolResult{
1972-
TextResultForLLM: "Invoking this tool produced an error. Detailed information is not available.",
1973-
ResultType: "failure",
1974-
Error: err.Error(),
1975-
ToolTelemetry: map[string]any{},
1976-
}}, nil
1977-
}
1978-
1979-
return &toolCallResponseV2{Result: result}, nil
1980-
}
1981-
1982-
// handlePermissionRequestV2 handles a v2-style permission.request RPC request from the server.
1983-
func (c *Client) handlePermissionRequestV2(req permissionRequestV2) (*permissionResponseV2, *jsonrpc2.Error) {
1984-
if req.SessionID == "" {
1985-
return nil, &jsonrpc2.Error{Code: -32602, Message: "invalid permission request payload"}
1986-
}
1987-
1988-
c.sessionsMux.Lock()
1989-
session, ok := c.sessions[req.SessionID]
1990-
c.sessionsMux.Unlock()
1991-
if !ok {
1992-
return nil, &jsonrpc2.Error{Code: -32602, Message: fmt.Sprintf("unknown session %s", req.SessionID)}
1993-
}
1994-
1995-
handler := session.getPermissionHandler()
1996-
if handler == nil {
1997-
return &permissionResponseV2{Result: &rpc.PermissionDecisionUserNotAvailable{}}, nil
1998-
}
1999-
2000-
invocation := PermissionInvocation{
2001-
SessionID: session.SessionID,
2002-
}
2003-
2004-
decision, err := handler(req.Request, invocation)
2005-
if err != nil {
2006-
return &permissionResponseV2{Result: &rpc.PermissionDecisionUserNotAvailable{}}, nil
2007-
}
2008-
if _, isNoResult := decision.(*rpc.PermissionDecisionNoResult); isNoResult {
2009-
return nil, &jsonrpc2.Error{Code: -32603, Message: noResultPermissionDirectRpcError}
2010-
}
2011-
if _, isNoResult := decision.(rpc.PermissionDecisionNoResult); isNoResult {
2012-
return nil, &jsonrpc2.Error{Code: -32603, Message: noResultPermissionDirectRpcError}
2013-
}
2014-
2015-
return &permissionResponseV2{Result: decision}, nil
2016-
}

0 commit comments

Comments
 (0)