diff --git a/dotnet/src/Session.cs b/dotnet/src/Session.cs index 4feeb9f95..45f093b10 100644 --- a/dotnet/src/Session.cs +++ b/dotnet/src/Session.cs @@ -147,6 +147,7 @@ public async Task SendAsync(MessageOptions options, CancellationToken ca /// 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. /// Thrown if the timeout is reached before the session becomes idle. + /// Thrown if the is cancelled. /// Thrown if the session has been disposed. /// /// @@ -201,7 +202,12 @@ void Handler(SessionEvent evt) cts.CancelAfter(effectiveTimeout); using var registration = cts.Token.Register(() => - tcs.TrySetException(new TimeoutException($"SendAndWaitAsync timed out after {effectiveTimeout}"))); + { + if (cancellationToken.IsCancellationRequested) + tcs.TrySetCanceled(cancellationToken); + else + tcs.TrySetException(new TimeoutException($"SendAndWaitAsync timed out after {effectiveTimeout}")); + }); return await tcs.Task; } diff --git a/dotnet/test/SessionTests.cs b/dotnet/test/SessionTests.cs index c9a152ce9..7b7dcafd7 100644 --- a/dotnet/test/SessionTests.cs +++ b/dotnet/test/SessionTests.cs @@ -403,6 +403,30 @@ public async Task SendAndWait_Throws_On_Timeout() Assert.Contains("timed out", ex.Message); } + [Fact] + public async Task SendAndWait_Throws_OperationCanceledException_When_Token_Cancelled() + { + var session = await Client.CreateSessionAsync(); + + // Set up wait for tool execution to start BEFORE sending + var toolStartTask = TestHelper.GetNextEventOfTypeAsync(session); + + using var cts = new CancellationTokenSource(); + + // Start SendAndWaitAsync - don't await it yet + var sendTask = session.SendAndWaitAsync( + new MessageOptions { Prompt = "run the shell command 'sleep 10' (note this works on both bash and PowerShell)" }, + cancellationToken: cts.Token); + + // Wait for the tool to begin executing before cancelling + await toolStartTask; + + // Cancel the token + cts.Cancel(); + + await Assert.ThrowsAnyAsync(() => sendTask); + } + [Fact] public async Task Should_Create_Session_With_Custom_Config_Dir() { diff --git a/test/snapshots/session/sendandwait_throws_operationcanceledexception_when_token_cancelled.yaml b/test/snapshots/session/sendandwait_throws_operationcanceledexception_when_token_cancelled.yaml new file mode 100644 index 000000000..a03140fa1 --- /dev/null +++ b/test/snapshots/session/sendandwait_throws_operationcanceledexception_when_token_cancelled.yaml @@ -0,0 +1,24 @@ +models: + - claude-sonnet-4.5 +conversations: + - messages: + - role: system + content: ${system} + - role: user + content: run the shell command 'sleep 10' (note this works on both bash and PowerShell) + - role: assistant + content: I'll run the sleep command for you. + - role: assistant + tool_calls: + - id: toolcall_0 + type: function + function: + name: report_intent + arguments: '{"intent":"Running sleep command"}' + - role: assistant + tool_calls: + - id: toolcall_1 + type: function + function: + name: ${shell} + arguments: '{"command":"sleep 10","description":"Execute sleep 10 command","initial_wait":15,"mode":"sync"}'