Skip to content

python-client: client-initiated turn cancellation (cancel) - #463

Merged
brentrager merged 1 commit into
mainfrom
python-client-cancel
Aug 18, 2026
Merged

python-client: client-initiated turn cancellation (cancel)#463
brentrager merged 1 commit into
mainfrom
python-client-cancel

Conversation

@brentrager

Copy link
Copy Markdown
Contributor

Problem

The wire protocol and all five servers already honor the cancel frame / cancelled event, but the Python async client had no way to send it — the missing half of the just-merged TypeScript client (#459). There was no "Stop button" surface.

Solution

Mirror #459 in the Python client (python/src/smooth_operator/):

  • SmoothAgentClient.cancel(request_id=..., session_id=None) — sends the {action: "cancel", requestId, sessionId?} frame (sync send, matching its confirm_tool_action / verify_otp siblings).
  • MessageTurn.cancel() — ergonomic "stop THIS turn" convenience; cancels using the turn's own request_id + originating session_id.
  • A terminal cancelled event settles the matching turn as a user-stop: it resolves (never raises), await turn yields the Cancelled event, the async for iterator ends cleanly, and turn.cancelled is True so a UI tells a stop apart from an error. Errors still raise ProtocolError.
  • CancelRequest / Cancelled are now first-class in the ClientAction / ServerEvent discriminated unions, the ActionType / EventType enums, and the validator's schema maps.
  • Idempotent: cancel with no active turn, or a cancelled with no matching in-flight turn, is a harmless no-op.

Reading user-stop vs error

turn = client.send_message(session_id=sid, message="write a novel")
turn.cancel()
final = await turn          # resolves — does NOT raise on a cancel
if turn.cancelled:          # final.type == "cancelled", final.status == 499
    ...                     # user stopped it
# an error still raises ProtocolError out of `await turn` / the async-for

Tests

5 new mock-transport tests in python/tests/test_client.py: turn/client cancel emit the correct frame, cancelled settles the matching turn as a user-stop (resolves, turn.cancelled == True, iterator ends cleanly), and idempotent no-ops (nothing running; late cancelled after completion). Full suite green (uv run pytest: 58 passed, 1 live-e2e skipped), ruff lint + format clean, compileall clean.

🤖 Generated with Claude Code

Mirror the TypeScript client's Stop-button surface (#459) in the Python async
client. The wire protocol and all five servers already honor the `cancel` frame
/ `cancelled` event; the Python client had no way to send it.

- SmoothAgentClient.cancel(request_id=, session_id=) sends the cancel frame.
- MessageTurn.cancel() is the ergonomic "stop THIS turn" convenience, using the
  turn's own request_id + originating session_id.
- A terminal `cancelled` event settles the matching turn as a user-stop: it
  RESOLVES (never raises), `await turn` yields the Cancelled event, the async
  iterator ends cleanly, and turn.cancelled is True so callers tell a stop from
  an error.
- CancelRequest / Cancelled are now first-class in the ClientAction / ServerEvent
  unions and the validator's schema maps.
- Idempotent: cancel with no active turn, or a cancelled with no matching turn,
  is a harmless no-op.

Tests (mock transport), README stop-button snippet, and a changeset included.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YbN45JeWDbcjvFqGJvmVD3
@changeset-bot

changeset-bot Bot commented Aug 18, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: ec3fbe5

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@smooai/smooth-operator Minor
@smooai/smooth-operator-web-chat-example Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@brentrager
brentrager merged commit f5493e9 into main Aug 18, 2026
3 checks passed
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.

1 participant