Add public SDK cancel endpoint and CloudClient method

- POST /v1/tasks/{task_id}/cancel: tasks:submit scoped, 200 for
  immediate/idempotent cancellation, 202 for newly recorded pending
  cancellation, 404 for unknown task, 409 for terminal task.
- TaskCancellationResponse{task_id, status} model.
- Widen list_tasks status_filter Literal to include "cancelled".
- CloudClient.cancel_task(task_id).
- SDK-level tests covering queued/assigned/idempotent/404/409/scope
  cases for both the router and CloudClient.

Task 5/9 of task-cancellation change.
This commit is contained in:
2026-07-15 18:39:00 +08:00
parent d3024b4810
commit 6776ac2f2d
6 changed files with 192 additions and 7 deletions
+17
View File
@@ -123,6 +123,22 @@ def test_client_unknown_task_raises(tmp_path) -> None:
client.get_task_status("does-not-exist")
def test_client_cancel_task_round_trip(tmp_path) -> None:
client, _ = _client_and_pool(tmp_path)
task_id = client.submit_task(goal="cancel me")["task_id"]
cancelled = client.cancel_task(task_id)
assert cancelled == {"task_id": task_id, "status": "cancelled"}
assert client.get_task_status(task_id)["status"] == "cancelled"
def test_client_cancel_unknown_task_raises(tmp_path) -> None:
client, _ = _client_and_pool(tmp_path)
with pytest.raises(httpx.HTTPStatusError):
client.cancel_task("does-not-exist")
def test_client_applies_bearer_token_to_every_public_method(tmp_path) -> None:
token = "sdk-secret"
provider = ConfiguredBearerAuthProvider(
@@ -152,6 +168,7 @@ def test_client_applies_bearer_token_to_every_public_method(tmp_path) -> None:
assert client.get_task_status(task_id)["status"] == "queued"
assert client.list_tasks()["total"] == 1
assert client.get_task_attempts(task_id) == []
assert client.cancel_task(task_id) == {"task_id": task_id, "status": "cancelled"}
assert client.list_devices() == []
assert client.list_hosts() == []
assert client.list_plugins() == []