Skip to content

feat(python): plumb AbortSignal through ToolInvocation for handler cancellation#1703

Open
gimenete wants to merge 4 commits into
github:mainfrom
gimenete:gimenete/feat-python-abort-signal
Open

feat(python): plumb AbortSignal through ToolInvocation for handler cancellation#1703
gimenete wants to merge 4 commits into
github:mainfrom
gimenete:gimenete/feat-python-abort-signal

Conversation

@gimenete

Copy link
Copy Markdown

Summary

Port of the Node.js AbortSignal implementation (#1433) to the Python SDK.

Adds cooperative cancellation support so tool handlers can detect and respond to session.abort() calls.

Changes

  • AbortSignal class with is_aborted property and await signal.wait() coroutine
  • AbortController class that creates and manages an AbortSignal
  • ToolInvocation.signal field — receives the session's current AbortSignal
  • CopilotSession.abort() triggers all in-flight handlers via the signal, then resets the controller for future tool calls
  • AbortSignal exported from the top-level copilot package

Usage

from copilot import define_tool, ToolInvocation

@define_tool(description="Fetch remote data")
async def fetch_data(params: Params, inv: ToolInvocation) -> str:
    if inv.signal.is_aborted:
        return "cancelled"
    # Pass signal to cooperative async operations
    async with aiohttp.ClientSession() as s:
        # Poll for abort
        while not inv.signal.is_aborted:
            ...

Backwards compatible

Handlers that don't consume the signal continue to work unchanged.

Closes #1433

…ncellation

Add cooperative cancellation support to the Python SDK so tool handlers
can detect and respond to session.abort() calls.

Changes:
- Add AbortSignal class with is_aborted property and wait() coroutine
- Add AbortController class to manage and trigger AbortSignal
- Add signal field to ToolInvocation (defaults to a fresh AbortSignal)
- Add _tool_abort_controller to CopilotSession; thread signal through
  _execute_tool_and_respond so every handler receives the current signal
- Update abort() to trigger in-flight handlers then reset the controller
- Update abort() docstring to document handler cancellation behavior
- Export AbortSignal from the top-level copilot package
- Add tests: is_aborted initially False, True after abort, wait() resolves,
  ToolInvocation carries AbortSignal, handler receives correct signal

Closes github#1433

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 17, 2026 12:29
@gimenete gimenete requested a review from a team as a code owner June 17, 2026 12:29

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds cooperative cancellation support for tool handlers by introducing an abort signal propagated from CopilotSession.abort() into each ToolInvocation.

Changes:

  • Introduces AbortSignal/AbortController and adds ToolInvocation.signal for cooperative cancellation.
  • Propagates a session-scoped abort signal into tool invocations and triggers it on session abort (then resets for future tool calls).
  • Adds unit tests validating abort signal behavior and propagation to tool handlers.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
python/copilot/tools.py Adds AbortSignal/AbortController and attaches an abort signal to ToolInvocation.
python/copilot/session.py Wires a session-level abort controller into tool invocation execution and triggers it from abort().
python/copilot/init.py Exposes AbortSignal in the public package API.
python/test_tools.py Adds tests for abort signal semantics and propagation via ToolInvocation.

Comment thread python/copilot/tools.py Outdated
Comment thread python/copilot/session.py Outdated
Comment thread python/copilot/session.py
Comment thread python/copilot/session.py Outdated
gimenete and others added 3 commits June 17, 2026 14:32
…all()

Replaces the single shared AbortController with per-call tracking in
_in_flight_tool_calls so individual handlers can be cancelled without
aborting the agentic loop.

Changes:
- Replace _tool_abort_controller with _in_flight_tool_calls dict keyed by
  tool_call_id; each _execute_tool_and_respond creates its own controller,
  stores it, and removes it in a finally block (with recycling guard)
- abort() now aborts every in-flight controller via _abort_in_flight_tool_calls()
- disconnect() aborts in-flight tool calls before sending session.destroy
- Add cancel_tool_call(tool_call_id) -> bool: signals only the targeted
  handler, returns True if found, False otherwise
- Add _abort_in_flight_tool_calls() private helper
- Add TestCancelToolCall tests: returns False for unknown id, returns True
  and sets signal for known id, and aborts ONLY the targeted handler while
  leaving sibling handlers unaffected

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Address Copilot review feedback: the previous docstring implied the
signal is always tied to CopilotSession.abort(), but manually-constructed
ToolInvocation() instances receive an independent AbortSignal that the
session never triggers.

Updated docstring now explains:
- Session-dispatched invocations: signal is set to the session's managed
  AbortSignal, triggered by abort() or cancel_tool_call()
- Manually-constructed invocations (e.g. in tests): each instance gets
  its own independent signal unless one is injected explicitly

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ibility

Change signal field type from AbortSignal (with default_factory) to
AbortSignal | None = None so the new field is fully optional and does
not change the construction contract for existing code.

The session always sets a real AbortSignal when dispatching tools;
manually-constructed invocations now default to None. Handlers that
consume the signal should guard for None or inject one explicitly.

Update tests accordingly: signal defaults to None, and an injected
signal is honored and aborts correctly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature request: Plumb AbortSignal through ToolInvocation so session.abort() can cancel in-flight tool handlers

2 participants