Skip to content

Commit 2b3bf6d

Browse files
authored
feat: Add more task state management methods to TaskUpdater (#208)
# Description This PR adds the missing task state helper methods to TaskUpdater. Thank you for opening a Pull Request! Before submitting your PR, there are a few things you can do to make sure it goes smoothly: - [x] Follow the [`CONTRIBUTING` Guide](https://github.com/google-a2a/a2a-python/blob/main/CONTRIBUTING.md). - [x] Make your Pull Request title in the <https://www.conventionalcommits.org/> specification. - Important Prefixes for [release-please](https://github.com/googleapis/release-please): - `fix:` which represents bug fixes, and correlates to a [SemVer](https://semver.org/) patch. - `feat:` represents a new feature, and correlates to a SemVer minor. - `feat!:`, or `fix!:`, `refactor!:`, etc., which represent a breaking change (indicated by the `!`) and will result in a SemVer major. - [x] Ensure the tests and linter pass (Run `nox -s format` from the repository root to format) - [x] Appropriate docs were updated (if necessary) Fixes #172 🦕
1 parent 325ad8c commit 2b3bf6d

File tree

2 files changed

+172
-0
lines changed

2 files changed

+172
-0
lines changed

src/a2a/server/tasks/task_updater.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,30 @@ async def start_work(self, message: Message | None = None) -> None:
134134
message=message,
135135
)
136136

137+
async def cancel(self, message: Message | None = None) -> None:
138+
"""Marks the task as cancelled and publishes a finalstatus update."""
139+
await self.update_status(
140+
TaskState.canceled, message=message, final=True
141+
)
142+
143+
async def requires_input(
144+
self, message: Message | None = None, final: bool = False
145+
) -> None:
146+
"""Marks the task as input required and publishes a status update."""
147+
await self.update_status(
148+
TaskState.input_required,
149+
message=message,
150+
final=final,
151+
)
152+
153+
async def requires_auth(
154+
self, message: Message | None = None, final: bool = False
155+
) -> None:
156+
"""Marks the task as auth required and publishes a status update."""
157+
await self.update_status(
158+
TaskState.auth_required, message=message, final=final
159+
)
160+
137161
def new_agent_message(
138162
self,
139163
parts: list[Part],

tests/server/tasks/test_task_updater.py

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,3 +324,151 @@ async def test_reject_with_message(task_updater, event_queue, sample_message):
324324
assert event.status.state == TaskState.rejected
325325
assert event.final is True
326326
assert event.status.message == sample_message
327+
328+
329+
@pytest.mark.asyncio
330+
async def test_requires_input_without_message(task_updater, event_queue):
331+
"""Test marking a task as input required without a message."""
332+
await task_updater.requires_input()
333+
334+
event_queue.enqueue_event.assert_called_once()
335+
event = event_queue.enqueue_event.call_args[0][0]
336+
337+
assert isinstance(event, TaskStatusUpdateEvent)
338+
assert event.status.state == TaskState.input_required
339+
assert event.final is False
340+
assert event.status.message is None
341+
342+
343+
@pytest.mark.asyncio
344+
async def test_requires_input_with_message(
345+
task_updater, event_queue, sample_message
346+
):
347+
"""Test marking a task as input required with a message."""
348+
await task_updater.requires_input(message=sample_message)
349+
350+
event_queue.enqueue_event.assert_called_once()
351+
event = event_queue.enqueue_event.call_args[0][0]
352+
353+
assert isinstance(event, TaskStatusUpdateEvent)
354+
assert event.status.state == TaskState.input_required
355+
assert event.final is False
356+
assert event.status.message == sample_message
357+
358+
359+
@pytest.mark.asyncio
360+
async def test_requires_input_final_true(task_updater, event_queue):
361+
"""Test marking a task as input required with final=True."""
362+
await task_updater.requires_input(final=True)
363+
364+
event_queue.enqueue_event.assert_called_once()
365+
event = event_queue.enqueue_event.call_args[0][0]
366+
367+
assert isinstance(event, TaskStatusUpdateEvent)
368+
assert event.status.state == TaskState.input_required
369+
assert event.final is True
370+
assert event.status.message is None
371+
372+
373+
@pytest.mark.asyncio
374+
async def test_requires_input_with_message_and_final(
375+
task_updater, event_queue, sample_message
376+
):
377+
"""Test marking a task as input required with message and final=True."""
378+
await task_updater.requires_input(message=sample_message, final=True)
379+
380+
event_queue.enqueue_event.assert_called_once()
381+
event = event_queue.enqueue_event.call_args[0][0]
382+
383+
assert isinstance(event, TaskStatusUpdateEvent)
384+
assert event.status.state == TaskState.input_required
385+
assert event.final is True
386+
assert event.status.message == sample_message
387+
388+
389+
@pytest.mark.asyncio
390+
async def test_requires_auth_without_message(task_updater, event_queue):
391+
"""Test marking a task as auth required without a message."""
392+
await task_updater.requires_auth()
393+
394+
event_queue.enqueue_event.assert_called_once()
395+
event = event_queue.enqueue_event.call_args[0][0]
396+
397+
assert isinstance(event, TaskStatusUpdateEvent)
398+
assert event.status.state == TaskState.auth_required
399+
assert event.final is False
400+
assert event.status.message is None
401+
402+
403+
@pytest.mark.asyncio
404+
async def test_requires_auth_with_message(
405+
task_updater, event_queue, sample_message
406+
):
407+
"""Test marking a task as auth required with a message."""
408+
await task_updater.requires_auth(message=sample_message)
409+
410+
event_queue.enqueue_event.assert_called_once()
411+
event = event_queue.enqueue_event.call_args[0][0]
412+
413+
assert isinstance(event, TaskStatusUpdateEvent)
414+
assert event.status.state == TaskState.auth_required
415+
assert event.final is False
416+
assert event.status.message == sample_message
417+
418+
419+
@pytest.mark.asyncio
420+
async def test_requires_auth_final_true(task_updater, event_queue):
421+
"""Test marking a task as auth required with final=True."""
422+
await task_updater.requires_auth(final=True)
423+
424+
event_queue.enqueue_event.assert_called_once()
425+
event = event_queue.enqueue_event.call_args[0][0]
426+
427+
assert isinstance(event, TaskStatusUpdateEvent)
428+
assert event.status.state == TaskState.auth_required
429+
assert event.final is True
430+
assert event.status.message is None
431+
432+
433+
@pytest.mark.asyncio
434+
async def test_requires_auth_with_message_and_final(
435+
task_updater, event_queue, sample_message
436+
):
437+
"""Test marking a task as auth required with message and final=True."""
438+
await task_updater.requires_auth(message=sample_message, final=True)
439+
440+
event_queue.enqueue_event.assert_called_once()
441+
event = event_queue.enqueue_event.call_args[0][0]
442+
443+
assert isinstance(event, TaskStatusUpdateEvent)
444+
assert event.status.state == TaskState.auth_required
445+
assert event.final is True
446+
assert event.status.message == sample_message
447+
448+
449+
@pytest.mark.asyncio
450+
async def test_cancel_without_message(task_updater, event_queue):
451+
"""Test marking a task as cancelled without a message."""
452+
await task_updater.cancel()
453+
454+
event_queue.enqueue_event.assert_called_once()
455+
event = event_queue.enqueue_event.call_args[0][0]
456+
457+
assert isinstance(event, TaskStatusUpdateEvent)
458+
assert event.status.state == TaskState.canceled
459+
assert event.final is True
460+
assert event.status.message is None
461+
462+
463+
@pytest.mark.asyncio
464+
async def test_cancel_with_message(task_updater, event_queue, sample_message):
465+
"""Test marking a task as cancelled with a message."""
466+
await task_updater.cancel(message=sample_message)
467+
468+
event_queue.enqueue_event.assert_called_once()
469+
event = event_queue.enqueue_event.call_args[0][0]
470+
471+
assert isinstance(event, TaskStatusUpdateEvent)
472+
assert event.status.state == TaskState.canceled
473+
assert event.final is True
474+
assert event.status.message == sample_message

0 commit comments

Comments
 (0)