You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 1e0a7d6
Browse filesBrowse the repository at this point in the historyBrowse files
Mark Pollack
committed
Cancel requests both ways with $/cancel_request
ACP v1 Cancellation (stable since 2026-06-29), in the client and agent
sessions alike.
Sending: disposing a request's Mono before its response (the caller,
timeout(), or the SDK request timeout) sends $/cancel_request once, after
the request itself was written. Never for a request already answered,
dismissed by close, or that failed to send; a subscriber that cancels
before the request is created sends nothing. The report comes from
MonoSink.onCancel, not onDispose, which also runs after an error. The
peer's late answer is expected and logged at DEBUG (the last 1,024
cancelled ids are remembered). The send runs on the timeout delivery
threads, never on the shared timer. Each subscription to a request now
gets its own id: a retry used to reuse it, and the first response
completed both.
Receiving: InboundRequests wraps a request's complete answer, so a
cancel and the handler's answer race inside one Mono (takeUntilOther):
exactly one response, the handler's or -32800. The request is cancellable
only once its handler is subscribed, and no longer once its value is
produced. Once cancelled, a handler error (an interrupted blocking
handler throws) counts as cancelled. A cancelled prompt ends its turn
before the answer is published; one already cancelled by session/cancel
answers stop reason cancelled instead of -32800, as does a prompt handler
that fails with a cancellation after session/cancel. $/cancel_request is
handled by the session, never offered to notification handlers, and on
the client not queued behind the ordered notification drain. Unknown and
answered ids are ignored; integer ids match whatever integer type the
mapper produced, and "7" is not 7.
Graceful cancel: RequestCancellation.cancelWhen(trigger), written into
a request's Reactor context, sends $/cancel_request when the trigger
fires but keeps waiting, so the request ends with the peer's answer
(its result or AcpError -32800). At most one $/cancel_request per
request, whichever way it is cancelled.
Internal cancellation: CancellationException, an interrupt, or an
AcpProtocolException with -32800 from a handler is answered -32800.
Closing a session cancels the requests it is still handling.
Streamable HTTP: $/cancel_request is connection-scoped both ways, like
every $/ method (the routing tables' rule, and the Rust SDK's): the
client posts it without Acp-Session-Id, the agent sends it on the
connection stream even when its params name a session. A cancel that
overtakes its request on another stream finds no request and is
ignored, as the spec allows. A received $/cancel_request whose
requestId is not a string or integer is ignored: a notification has no
response to carry -32602. CancelRequestNotification carries _meta, and
its MetaCoverageTest exclusion is gone.
Also corrects the G0f note that a blocked sync handler is not
interrupted: the sync handler schedulers interrupt on cancel
(CHANGELOG, AcpAgent, AcpAgentSupport and @cancel Javadoc).
Cross-SDK suite: the Java interop client and agent run the three
cancel-request steps (client, agent, unknown id) and their P2 expected
failures are deleted; they pass Java<->Java and Java<->TypeScript on
stdio, Streamable HTTP and WebSocket, and Java<->Python where Python
can. Two Python peer gaps are declared: the Python client routes no
$/cancel_request, so an agent's cancelled fs read is never answered
-32800 and the client's own step sees the prompt end late.
Tests: CancelRequestTest (session level, both sides), an end-to-end test
in both directions plus a graceful cancel (red on main), InboundRequestsLincheckTest (exactly
one response, no table leak), and PendingResponsesLincheckTest extended
(a cancel is sent iff the caller gave up before any signal, at most
once). Each model fails on a planted bug.
Copy file name to clipboardExpand all lines: acp-core/src/main/java/com/agentclientprotocol/sdk/agent/AcpAgent.java
+1-1Lines changed: 1 addition & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -839,7 +839,7 @@ public SyncAgentBuilder requestTimeout(Duration timeout) {
839
839
840
840
/**
841
841
* Sets how long a prompt handler has to answer after {@code session/cancel}. When it
842
-
* passes, the agent cancels the handler (a sync handler blocked on its thread is not interrupted; what it sends afterwards is its own) and answers the prompt itself with stop
842
+
* passes, the agent cancels the handler (a sync handler blocked on its thread is interrupted; what it sends if it keeps running is its own) and answers the prompt itself with stop
843
843
* reason {@code cancelled}, as ACP requires of a cancelled prompt, which ends the
844
844
* turn so the session accepts a new prompt. Updates the handler sent before that
845
845
* answer reach the client first. Default: 60 seconds
0 commit comments