Skip to content

feat(nodejs): plumb AbortSignal through ToolInvocation (#1433)#1701

Open
gimenete wants to merge 2 commits into
github:mainfrom
gimenete:gimenete/feat-abort-signal-tool-invocation
Open

feat(nodejs): plumb AbortSignal through ToolInvocation (#1433)#1701
gimenete wants to merge 2 commits into
github:mainfrom
gimenete:gimenete/feat-abort-signal-tool-invocation

Conversation

@gimenete

Copy link
Copy Markdown

Summary

Implements the feature requested in #1433 for the Node.js SDK: plumb an AbortSignal through ToolInvocation so that session.abort() (and a new session.cancelToolCall(toolCallId)) can cooperatively cancel in-flight tool handlers.

Previously session.abort() only cancelled the agentic loop — a currently-executing tool handler ran to completion regardless, forcing consumers to implement OS-level process-tree kills.

Changes

  • ToolInvocation now carries a required signal: AbortSignal that aborts when session.abort() or session.cancelToolCall(toolCallId) is invoked while the handler is in flight.
  • session.abort() aborts the signals of all in-flight tool handlers before sending the abort RPC.
  • session.cancelToolCall(toolCallId) (new) cancels a single in-flight handler without aborting the broader agentic loop; returns true if a matching call was found.
  • session.disconnect() aborts in-flight handlers so they can release resources.
  • In-flight handlers are tracked in a Map<toolCallId, AbortController>.

Backwards compatibility

Handlers that ignore the signal continue to run to completion — existing handlers work unchanged.

Tests

Extended the abort e2e test to assert the handler's AbortSignal fires when session.abort() is called. Typecheck, lint, format check, and unit tests all pass.

Closes #1433

Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com

Add a cooperative cancellation signal to tool handlers so session.abort()
(and a new session.cancelToolCall(toolCallId)) can cancel in-flight tool
handlers. Handlers opt in via the AbortSignal on their ToolInvocation;
handlers that ignore it run to completion, preserving existing behavior.

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 11:02
@gimenete gimenete requested a review from a team as a code owner June 17, 2026 11:02

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.

Add cooperative cancellation for Node.js tool handlers by wiring an AbortSignal into tool invocations, aborting in-flight handlers on session abort/disconnect, and documenting/testing the behavior.

Changes:

  • Pass an AbortSignal to tool handlers and abort it on session.abort()/disconnect().
  • Track in-flight tool calls and add cancelToolCall(toolCallId) to abort a single handler.
  • Extend docs and add an e2e assertion that tool-handler signals are aborted.

Reviewed changes

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

File Description
nodejs/test/e2e/abort.e2e.test.ts Adds an e2e check that tool handler AbortSignal is triggered by session.abort()
nodejs/src/types.ts Extends ToolInvocation with a signal: AbortSignal and documentation
nodejs/src/session.ts Tracks in-flight tool calls via AbortController, aborts them on abort()/disconnect(), adds cancelToolCall()
nodejs/README.md Documents cooperative tool cancellation and the new cancelToolCall API

Comment thread nodejs/test/e2e/abort.e2e.test.ts Outdated
Comment on lines +102 to +105
let signalAbortedResolve!: (value: void) => void;
const signalAborted = new Promise<void>((resolve) => {
signalAbortedResolve = resolve;
});
Comment thread nodejs/test/e2e/abort.e2e.test.ts Outdated
handler: async ({ value }) => {
handler: async ({ value }, { signal }) => {
toolStartedResolve(value);
signal.addEventListener("abort", () => signalAbortedResolve());
Comment thread nodejs/test/e2e/abort.e2e.test.ts Outdated
handler: async ({ value }) => {
handler: async ({ value }, { signal }) => {
toolStartedResolve(value);
signal.addEventListener("abort", () => signalAbortedResolve());
Comment thread nodejs/src/types.ts
Comment on lines 475 to +486
toolCallId: string;
toolName: string;
arguments: unknown;
/**
* An `AbortSignal` that aborts when `session.abort()` or
* `session.cancelToolCall(toolCallId)` is invoked while this handler is
* in flight. Handlers may opt in to cooperative cancellation by forwarding
* it to abortable APIs (`fetch(url, { signal })`, `child_process.spawn`,
* etc.) or by checking `signal.aborted`. Handlers that ignore it continue
* to run to completion, preserving existing behavior.
*/
signal: AbortSignal;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Keeping signal required on purpose for cross-SDK consistency — the same field is non-optional in the Python, Go, .NET, Rust, and Java equivalents, and the SDK always injects a live signal at runtime, so handlers never have to null-check it. The only friction is for consumers who hand-construct/mock ToolInvocation in tests, which is a one-line addition (signal: new AbortController().signal). Given it's a new field on a handler-input type rather than a return type, the runtime guarantee felt worth the minor upgrade cost.

Comment thread nodejs/src/session.ts
Comment on lines +1267 to +1274
cancelToolCall(toolCallId: string): boolean {
const controller = this.inFlightToolCalls.get(toolCallId);
if (!controller) {
return false;
}
controller.abort();
return true;
}
- Fix signalAbortedResolve type to () => void in abort e2e test
- Handle already-aborted signal and use { once: true } listener
- cancelToolCall now removes the in-flight entry so repeat/completed
  ids return false (consistent with other SDKs)
- Add e2e test covering cancelToolCall (true for in-flight, false for
  unknown and already-cancelled ids) with recorded snapshot

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