Skip to content

Commit b5826ca

Browse files
Hide StreamJsonRpc implementation detail behind IOException (#202)
* Initial plan * Wrap StreamJsonRpc exceptions with CopilotRpcException Co-authored-by: SteveSandersonMS <1101362+SteveSandersonMS@users.noreply.github.com> * Add explicit using System.IO for IOException Co-authored-by: SteveSandersonMS <1101362+SteveSandersonMS@users.noreply.github.com> * Only wrap RPC invocation calls in try-catch blocks Co-authored-by: SteveSandersonMS <1101362+SteveSandersonMS@users.noreply.github.com> * Remove implementation details from CopilotRpcException docs Co-authored-by: SteveSandersonMS <1101362+SteveSandersonMS@users.noreply.github.com> * Replace CopilotRpcException with IOException and add RPC wrapper methods Co-authored-by: SteveSandersonMS <1101362+SteveSandersonMS@users.noreply.github.com> * Catch RemoteRpcException, refactor Session.InvokeRpcAsync, remove unused using Co-authored-by: SteveSandersonMS <1101362+SteveSandersonMS@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: SteveSandersonMS <1101362+SteveSandersonMS@users.noreply.github.com>
1 parent 0554463 commit b5826ca

4 files changed

Lines changed: 44 additions & 29 deletions

File tree

dotnet/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -452,9 +452,9 @@ try
452452
var session = await client.CreateSessionAsync();
453453
await session.SendAsync(new MessageOptions { Prompt = "Hello" });
454454
}
455-
catch (StreamJsonRpc.RemoteInvocationException ex)
455+
catch (IOException ex)
456456
{
457-
Console.Error.WriteLine($"JSON-RPC Error: {ex.Message}");
457+
Console.Error.WriteLine($"Communication Error: {ex.Message}");
458458
}
459459
catch (Exception ex)
460460
{

dotnet/src/Client.cs

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -347,8 +347,8 @@ public async Task<CopilotSession> CreateSessionAsync(SessionConfig? config = nul
347347
config?.DisabledSkills,
348348
config?.InfiniteSessions);
349349

350-
var response = await connection.Rpc.InvokeWithCancellationAsync<CreateSessionResponse>(
351-
"session.create", [request], cancellationToken);
350+
var response = await InvokeRpcAsync<CreateSessionResponse>(
351+
connection.Rpc, "session.create", [request], cancellationToken);
352352

353353
var session = new CopilotSession(response.SessionId, connection.Rpc, response.WorkspacePath);
354354
session.RegisterTools(config?.Tools ?? []);
@@ -404,8 +404,8 @@ public async Task<CopilotSession> ResumeSessionAsync(string sessionId, ResumeSes
404404
config?.SkillDirectories,
405405
config?.DisabledSkills);
406406

407-
var response = await connection.Rpc.InvokeWithCancellationAsync<ResumeSessionResponse>(
408-
"session.resume", [request], cancellationToken);
407+
var response = await InvokeRpcAsync<ResumeSessionResponse>(
408+
connection.Rpc, "session.resume", [request], cancellationToken);
409409

410410
var session = new CopilotSession(response.SessionId, connection.Rpc, response.WorkspacePath);
411411
session.RegisterTools(config?.Tools ?? []);
@@ -461,8 +461,8 @@ public async Task<PingResponse> PingAsync(string? message = null, CancellationTo
461461
{
462462
var connection = await EnsureConnectedAsync(cancellationToken);
463463

464-
return await connection.Rpc.InvokeWithCancellationAsync<PingResponse>(
465-
"ping", [new PingRequest { Message = message }], cancellationToken);
464+
return await InvokeRpcAsync<PingResponse>(
465+
connection.Rpc, "ping", [new PingRequest { Message = message }], cancellationToken);
466466
}
467467

468468
/// <summary>
@@ -475,8 +475,8 @@ public async Task<GetStatusResponse> GetStatusAsync(CancellationToken cancellati
475475
{
476476
var connection = await EnsureConnectedAsync(cancellationToken);
477477

478-
return await connection.Rpc.InvokeWithCancellationAsync<GetStatusResponse>(
479-
"status.get", [], cancellationToken);
478+
return await InvokeRpcAsync<GetStatusResponse>(
479+
connection.Rpc, "status.get", [], cancellationToken);
480480
}
481481

482482
/// <summary>
@@ -489,8 +489,8 @@ public async Task<GetAuthStatusResponse> GetAuthStatusAsync(CancellationToken ca
489489
{
490490
var connection = await EnsureConnectedAsync(cancellationToken);
491491

492-
return await connection.Rpc.InvokeWithCancellationAsync<GetAuthStatusResponse>(
493-
"auth.getStatus", [], cancellationToken);
492+
return await InvokeRpcAsync<GetAuthStatusResponse>(
493+
connection.Rpc, "auth.getStatus", [], cancellationToken);
494494
}
495495

496496
/// <summary>
@@ -503,8 +503,8 @@ public async Task<List<ModelInfo>> ListModelsAsync(CancellationToken cancellatio
503503
{
504504
var connection = await EnsureConnectedAsync(cancellationToken);
505505

506-
var response = await connection.Rpc.InvokeWithCancellationAsync<GetModelsResponse>(
507-
"models.list", [], cancellationToken);
506+
var response = await InvokeRpcAsync<GetModelsResponse>(
507+
connection.Rpc, "models.list", [], cancellationToken);
508508

509509
return response.Models;
510510
}
@@ -528,8 +528,8 @@ public async Task<List<ModelInfo>> ListModelsAsync(CancellationToken cancellatio
528528
{
529529
var connection = await EnsureConnectedAsync(cancellationToken);
530530

531-
var response = await connection.Rpc.InvokeWithCancellationAsync<GetLastSessionIdResponse>(
532-
"session.getLastId", [], cancellationToken);
531+
var response = await InvokeRpcAsync<GetLastSessionIdResponse>(
532+
connection.Rpc, "session.getLastId", [], cancellationToken);
533533

534534
return response.SessionId;
535535
}
@@ -554,8 +554,8 @@ public async Task DeleteSessionAsync(string sessionId, CancellationToken cancell
554554
{
555555
var connection = await EnsureConnectedAsync(cancellationToken);
556556

557-
var response = await connection.Rpc.InvokeWithCancellationAsync<DeleteSessionResponse>(
558-
"session.delete", [new DeleteSessionRequest(sessionId)], cancellationToken);
557+
var response = await InvokeRpcAsync<DeleteSessionResponse>(
558+
connection.Rpc, "session.delete", [new DeleteSessionRequest(sessionId)], cancellationToken);
559559

560560
if (!response.Success)
561561
{
@@ -584,12 +584,24 @@ public async Task<List<SessionMetadata>> ListSessionsAsync(CancellationToken can
584584
{
585585
var connection = await EnsureConnectedAsync(cancellationToken);
586586

587-
var response = await connection.Rpc.InvokeWithCancellationAsync<ListSessionsResponse>(
588-
"session.list", [], cancellationToken);
587+
var response = await InvokeRpcAsync<ListSessionsResponse>(
588+
connection.Rpc, "session.list", [], cancellationToken);
589589

590590
return response.Sessions;
591591
}
592592

593+
internal static async Task<T> InvokeRpcAsync<T>(JsonRpc rpc, string method, object?[]? args, CancellationToken cancellationToken)
594+
{
595+
try
596+
{
597+
return await rpc.InvokeWithCancellationAsync<T>(method, args, cancellationToken);
598+
}
599+
catch (StreamJsonRpc.RemoteRpcException ex)
600+
{
601+
throw new IOException($"Communication error with Copilot CLI: {ex.Message}", ex);
602+
}
603+
}
604+
593605
private Task<Connection> EnsureConnectedAsync(CancellationToken cancellationToken)
594606
{
595607
if (_connectionTask is null && !_options.AutoStart)
@@ -604,8 +616,8 @@ private Task<Connection> EnsureConnectedAsync(CancellationToken cancellationToke
604616
private async Task VerifyProtocolVersionAsync(Connection connection, CancellationToken cancellationToken)
605617
{
606618
var expectedVersion = SdkProtocolVersion.GetVersion();
607-
var pingResponse = await connection.Rpc.InvokeWithCancellationAsync<PingResponse>(
608-
"ping", [new PingRequest()], cancellationToken);
619+
var pingResponse = await InvokeRpcAsync<PingResponse>(
620+
connection.Rpc, "ping", [new PingRequest()], cancellationToken);
609621

610622
if (!pingResponse.ProtocolVersion.HasValue)
611623
{

dotnet/src/Session.cs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ internal CopilotSession(string sessionId, JsonRpc rpc, string? workspacePath = n
8080
WorkspacePath = workspacePath;
8181
}
8282

83+
private Task<T> InvokeRpcAsync<T>(string method, object?[]? args, CancellationToken cancellationToken) =>
84+
CopilotClient.InvokeRpcAsync<T>(_rpc, method, args, cancellationToken);
85+
8386
/// <summary>
8487
/// Sends a message to the Copilot session and waits for the response.
8588
/// </summary>
@@ -118,7 +121,7 @@ public async Task<string> SendAsync(MessageOptions options, CancellationToken ca
118121
Mode = options.Mode
119122
};
120123

121-
var response = await _rpc.InvokeWithCancellationAsync<SendMessageResponse>(
124+
var response = await InvokeRpcAsync<SendMessageResponse>(
122125
"session.send", [request], cancellationToken);
123126

124127
return response.MessageId;
@@ -351,7 +354,7 @@ internal async Task<PermissionRequestResult> HandlePermissionRequestAsync(JsonEl
351354
/// </example>
352355
public async Task<IReadOnlyList<SessionEvent>> GetMessagesAsync(CancellationToken cancellationToken = default)
353356
{
354-
var response = await _rpc.InvokeWithCancellationAsync<GetMessagesResponse>(
357+
var response = await InvokeRpcAsync<GetMessagesResponse>(
355358
"session.getMessages", [new GetMessagesRequest { SessionId = SessionId }], cancellationToken);
356359

357360
return response.Events
@@ -385,7 +388,7 @@ public async Task<IReadOnlyList<SessionEvent>> GetMessagesAsync(CancellationToke
385388
/// </example>
386389
public async Task AbortAsync(CancellationToken cancellationToken = default)
387390
{
388-
await _rpc.InvokeWithCancellationAsync<object>(
391+
await InvokeRpcAsync<object>(
389392
"session.abort", [new SessionAbortRequest { SessionId = SessionId }], cancellationToken);
390393
}
391394

@@ -416,8 +419,8 @@ await _rpc.InvokeWithCancellationAsync<object>(
416419
/// </example>
417420
public async ValueTask DisposeAsync()
418421
{
419-
await _rpc.InvokeWithCancellationAsync<object>(
420-
"session.destroy", [new SessionDestroyRequest() { SessionId = SessionId }]);
422+
await InvokeRpcAsync<object>(
423+
"session.destroy", [new SessionDestroyRequest() { SessionId = SessionId }], CancellationToken.None);
421424

422425
_eventHandlers.Clear();
423426
_toolHandlers.Clear();

dotnet/test/SessionTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public async Task ShouldCreateAndDestroySessions()
2626

2727
await session.DisposeAsync();
2828

29-
var ex = await Assert.ThrowsAsync<StreamJsonRpc.RemoteInvocationException>(() => session.GetMessagesAsync());
29+
var ex = await Assert.ThrowsAsync<IOException>(() => session.GetMessagesAsync());
3030
Assert.Contains("not found", ex.Message, StringComparison.OrdinalIgnoreCase);
3131
}
3232

@@ -192,7 +192,7 @@ public async Task Should_Resume_A_Session_Using_A_New_Client()
192192
[Fact]
193193
public async Task Should_Throw_Error_When_Resuming_Non_Existent_Session()
194194
{
195-
await Assert.ThrowsAsync<StreamJsonRpc.RemoteInvocationException>(() =>
195+
await Assert.ThrowsAsync<IOException>(() =>
196196
Client.ResumeSessionAsync("non-existent-session-id"));
197197
}
198198

0 commit comments

Comments
 (0)