dotnet: plumb CancellationToken through ToolInvocation for cooperative cancellation#1704
Open
gimenete wants to merge 3 commits into
Open
dotnet: plumb CancellationToken through ToolInvocation for cooperative cancellation#1704gimenete wants to merge 3 commits into
gimenete wants to merge 3 commits into
Conversation
…e cancellation Add a CancellationToken property to ToolInvocation that is cancelled when AbortAsync() or CancelToolCall(toolCallId) is called while a tool handler is in flight. Key changes: - ToolInvocation.CancellationToken: populated per-invocation; cancelled on abort or targeted cancel - ExecuteToolAndRespondAsync: creates a CancellationTokenSource per call, stores it in _inFlightToolCalls, passes its token to both ToolInvocation and tool.InvokeAsync (so direct CancellationToken parameters also work) - AbortAsync: calls AbortInFlightToolCalls() before the RPC - CancelToolCall(string): new public method to cancel a single in-flight handler without aborting the agentic loop - DisposeAsync: aborts and clears in-flight tool calls before session.destroy - README: document both CancellationToken parameter and ToolInvocation patterns, with CancelToolCall example - Unit tests: verify CancellationToken parameter binding and ToolInvocation token exposure Handlers that ignore the token continue to run to completion, preserving existing behavior. Fixes github#1433 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds cooperative cancellation support for in-flight tool handlers, enabling session-wide and per-tool-call cancellation via CancellationToken.
Changes:
- Tracks in-flight tool calls with
CancellationTokenSourceand passes tokens into tool invocations. - Adds
ToolInvocation.CancellationTokenand a newCopilotSession.CancelToolCall(toolCallId)API. - Adds unit tests and README documentation describing cancellation usage patterns.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| dotnet/test/Unit/CopilotToolTests.cs | Adds unit tests for binding CancellationToken to tool handlers and ToolInvocation. |
| dotnet/src/Types.cs | Extends ToolInvocation with a CancellationToken property and documentation. |
| dotnet/src/Session.cs | Implements in-flight tool call tracking + cooperative cancellation (AbortAsync cancels tools; adds CancelToolCall). |
| dotnet/README.md | Documents new cancellation behavior, including CancelToolCall and handler patterns. |
Comments suppressed due to low confidence (1)
dotnet/src/Session.cs:1
- This introduces new externally observable behavior (
AbortAsyncnow cancels in-flight tool tokens;CancelToolCallreturns true/false and signals cancellation). Consider adding unit tests that (1) start a tool handler that waits on the provided token, (2) verifyCancelToolCall(toolCallId)returnstrueand causes the handler to observe cancellation, and (3) verifyAbortAsynccancels in-flight tool handlers’ tokens.
/*---------------------------------------------------------------------------------------------
- Use requestId (not toolCallId) as the dictionary key in _inFlightToolCalls. requestId is unique per RPC request, so toolCallId reuse can never overwrite an active entry. CancelToolCall scans by toolCallId (O(n) over the typically tiny number of in-flight calls) so the public API is unchanged. - CancelToolCall: capture the matching CancellationTokenSource under the lock, then call Cancel() after releasing it. This avoids CancellationToken callback invocations running while the lock is held, preventing potential deadlocks if a callback (directly or indirectly) touches session state. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
ToolInvocation is registered in the source-gen JSON context. The new CancellationToken property is a runtime-only handle that must never be serialized over the wire (it exposes a WaitHandle getter that allocates). Marking it [JsonIgnore] keeps the DTO wire-compatible and keeps the new field optional/backwards compatible — existing code and serialized payloads are unaffected. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements #1433 for the .NET SDK.
Adds a
CancellationTokentoToolInvocationthat is cancelled whenAbortAsync()or the newCancelToolCall(toolCallId)is called while a tool handler is in flight.Changes
Types.csCancellationToken CancellationToken { get; set; }toToolInvocationSession.cs_inFlightToolCallsdictionary (guarded by a lock) to track activeCancellationTokenSourceinstancesExecuteToolAndRespondAsync: creates aCancellationTokenSourceper invocation, stores it, passes its token toToolInvocation.CancellationTokenand totool.InvokeAsync(enabling the directCancellationTokenparameter pattern automatically viaAIFunctionFactory)AbortAsync: cancels all in-flight tool handlers before sending the RPC abortCancelToolCall(string toolCallId): cancels a single in-flight handler without aborting the agentic loopDisposeAsync: aborts and clears in-flight tool calls beforesession.destroyREADME.mdAbortAsyncdocs to mention cancellationCancelToolCallmethod entryToolInvocation)Tests
CancellationTokenparameter binding andToolInvocation.CancellationTokenexposureBehavior
Handlers that ignore the token continue to run to completion — fully backwards compatible.