Skip to content

Commit 96e2501

Browse files
committed
refactor: define terminal and interrupted task states once in a2a.utils.constants
1 parent e615a8d commit 96e2501

11 files changed

Lines changed: 57 additions & 73 deletions

File tree

‎src/a2a/server/agent_execution/active_task.py‎

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@
7575
TaskStatus,
7676
TaskStatusUpdateEvent,
7777
)
78+
from a2a.utils.constants import (
79+
INTERRUPTED_TASK_STATES,
80+
TERMINAL_TASK_STATES,
81+
)
7882
from a2a.utils.errors import (
7983
InternalError,
8084
InvalidAgentResponseError,
@@ -86,18 +90,6 @@
8690
logger = logging.getLogger(__name__)
8791

8892

89-
TERMINAL_TASK_STATES = {
90-
TaskState.TASK_STATE_COMPLETED,
91-
TaskState.TASK_STATE_CANCELED,
92-
TaskState.TASK_STATE_FAILED,
93-
TaskState.TASK_STATE_REJECTED,
94-
}
95-
INTERRUPTED_TASK_STATES = {
96-
TaskState.TASK_STATE_AUTH_REQUIRED,
97-
TaskState.TASK_STATE_INPUT_REQUIRED,
98-
}
99-
100-
10193
class _RequestStarted:
10294
def __init__(self, request_id: uuid.UUID, request_context: RequestContext):
10395
self.request_id = request_id

‎src/a2a/server/cluster/database_task_store.py‎

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,20 +44,12 @@
4444
Task,
4545
TaskState,
4646
)
47+
from a2a.utils.constants import TERMINAL_TASK_STATES
4748
from a2a.utils.proto_utils import to_stream_response
4849

4950

5051
logger = logging.getLogger(__name__)
5152

52-
_TERMINAL_STATES = frozenset(
53-
{
54-
TaskState.TASK_STATE_COMPLETED,
55-
TaskState.TASK_STATE_CANCELED,
56-
TaskState.TASK_STATE_FAILED,
57-
TaskState.TASK_STATE_REJECTED,
58-
}
59-
)
60-
6153
# DB contention retry
6254
_MAX_ATTEMPTS = 5
6355
_RETRY_DELAY_S = 0.02
@@ -195,7 +187,10 @@ async def _save_once(
195187
if task.status.state == TaskState.TASK_STATE_CANCELED:
196188
stored = await self._lock_version(session, task.id, owner)
197189
current = await self._current_task(session, task.id, owner)
198-
if current is None or current.status.state in _TERMINAL_STATES:
190+
if (
191+
current is None
192+
or current.status.state in TERMINAL_TASK_STATES
193+
):
199194
raise ConcurrentTaskModificationError(task.id)
200195
new_version = (stored or 0) + 1
201196
if stored is None:

‎src/a2a/server/request_handlers/default_request_handler.py‎

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
TaskPushNotificationConfig,
5454
TaskState,
5555
)
56+
from a2a.utils.constants import TERMINAL_TASK_STATES
5657
from a2a.utils.errors import (
5758
ExtendedAgentCardNotConfiguredError,
5859
InternalError,
@@ -74,13 +75,6 @@
7475

7576
logger = logging.getLogger(__name__)
7677

77-
TERMINAL_TASK_STATES = {
78-
TaskState.TASK_STATE_COMPLETED,
79-
TaskState.TASK_STATE_CANCELED,
80-
TaskState.TASK_STATE_FAILED,
81-
TaskState.TASK_STATE_REJECTED,
82-
}
83-
8478

8579
@trace_class(kind=SpanKind.SERVER)
8680
class LegacyRequestHandler(RequestHandler):

‎src/a2a/server/request_handlers/default_request_handler_v2.py‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,6 @@
1515
RequestContextBuilder,
1616
SimpleRequestContextBuilder,
1717
)
18-
from a2a.server.agent_execution.active_task import (
19-
INTERRUPTED_TASK_STATES,
20-
TERMINAL_TASK_STATES,
21-
)
2218
from a2a.server.agent_execution.active_task_registry import ActiveTaskRegistry
2319
from a2a.server.agent_execution.plugin_agent_executor import (
2420
PluginAgentExecutor,
@@ -57,6 +53,10 @@
5753
TaskState,
5854
TaskStatusUpdateEvent,
5955
)
56+
from a2a.utils.constants import (
57+
INTERRUPTED_TASK_STATES,
58+
TERMINAL_TASK_STATES,
59+
)
6060
from a2a.utils.errors import (
6161
ExtendedAgentCardNotConfiguredError,
6262
InternalError,

‎src/a2a/server/tasks/task_updater.py‎

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
TaskStatus,
2222
TaskStatusUpdateEvent,
2323
)
24+
from a2a.utils.constants import TERMINAL_TASK_STATES
2425

2526

2627
class TaskUpdater:
@@ -51,12 +52,6 @@ def __init__(
5152
self.context_id = context_id
5253
self._lock = asyncio.Lock()
5354
self._terminal_state_reached = False
54-
self._terminal_states = {
55-
TaskState.TASK_STATE_COMPLETED,
56-
TaskState.TASK_STATE_CANCELED,
57-
TaskState.TASK_STATE_FAILED,
58-
TaskState.TASK_STATE_REJECTED,
59-
}
6055
self._artifact_id_generator = (
6156
artifact_id_generator if artifact_id_generator else UUIDGenerator()
6257
)
@@ -105,7 +100,7 @@ async def update_status(
105100
metadata=metadata,
106101
status=status,
107102
)
108-
if state in self._terminal_states:
103+
if state in TERMINAL_TASK_STATES:
109104
self._terminal_state_reached = True
110105
await self.event_queue.enqueue_event(event)
111106

‎src/a2a/utils/constants.py‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
from enum import Enum
44

5+
from a2a.types.a2a_pb2 import TaskState
6+
57

68
AGENT_CARD_WELL_KNOWN_PATH = '/.well-known/agent-card.json'
79
DEFAULT_RPC_URL = '/'
@@ -29,3 +31,19 @@ class TransportProtocol(str, Enum):
2931
PROTOCOL_VERSION_1_0 = '1.0'
3032
PROTOCOL_VERSION_0_3 = '0.3'
3133
PROTOCOL_VERSION_CURRENT = PROTOCOL_VERSION_1_0
34+
35+
TERMINAL_TASK_STATES: frozenset[TaskState.ValueType] = frozenset(
36+
{
37+
TaskState.TASK_STATE_COMPLETED,
38+
TaskState.TASK_STATE_CANCELED,
39+
TaskState.TASK_STATE_FAILED,
40+
TaskState.TASK_STATE_REJECTED,
41+
}
42+
)
43+
44+
INTERRUPTED_TASK_STATES: frozenset[TaskState.ValueType] = frozenset(
45+
{
46+
TaskState.TASK_STATE_INPUT_REQUIRED,
47+
TaskState.TASK_STATE_AUTH_REQUIRED,
48+
}
49+
)

‎tests/integration/test_scenarios.py‎

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
TaskStatusUpdateEvent,
5555
)
5656
from a2a.utils import TransportProtocol
57+
from a2a.utils.constants import TERMINAL_TASK_STATES
5758
from a2a.utils.errors import (
5859
InvalidAgentResponseError,
5960
TaskNotCancelableError,
@@ -2379,12 +2380,6 @@ async def cancel(
23792380
# executors below define cancel() as an empty teardown, which is what
23802381
# InputRequiredAgent, SlowAgent and DummyAgentExecutor use in this file and what
23812382
# a real cleanup-only executor looks like.
2382-
_TERMINAL_STATES = {
2383-
TaskState.TASK_STATE_CANCELED,
2384-
TaskState.TASK_STATE_COMPLETED,
2385-
TaskState.TASK_STATE_FAILED,
2386-
TaskState.TASK_STATE_REJECTED,
2387-
}
23882383

23892384

23902385
async def _start_task(client, text):
@@ -2435,7 +2430,7 @@ async def cancel(
24352430
await client.cancel_task(CancelTaskRequest(id=task_id))
24362431
task_after = await client.get_task(GetTaskRequest(id=task_id))
24372432

2438-
assert task_after.status.state in _TERMINAL_STATES
2433+
assert task_after.status.state in TERMINAL_TASK_STATES
24392434

24402435

24412436
@pytest.mark.timeout(5.0)

‎tests/server/cluster/conftest.py‎

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,19 +37,11 @@
3737
Task,
3838
TaskState,
3939
)
40+
from a2a.utils.constants import TERMINAL_TASK_STATES
4041

4142

4243
# --- In-memory cluster doubles ----------------------------------------------
4344

44-
_TERMINAL_STATES = frozenset(
45-
{
46-
TaskState.TASK_STATE_COMPLETED,
47-
TaskState.TASK_STATE_CANCELED,
48-
TaskState.TASK_STATE_FAILED,
49-
TaskState.TASK_STATE_REJECTED,
50-
}
51-
)
52-
5345

5446
class VersionedInMemoryTaskStore(VersionedTaskStore):
5547
"""`VersionedTaskStore` backed by in-process dictionaries.
@@ -95,7 +87,10 @@ async def save(
9587
stored = self._current_version_locked(owner, task_id=task.id)
9688
if task.status.state == TaskState.TASK_STATE_CANCELED:
9789
current = await self._store.get(task.id, context)
98-
if current is None or current.status.state in _TERMINAL_STATES:
90+
if (
91+
current is None
92+
or current.status.state in TERMINAL_TASK_STATES
93+
):
9994
raise ConcurrentTaskModificationError(task.id)
10095
elif not prev_version.is_missing:
10196
if stored == 0:

‎tests/server/request_handlers/test_default_request_handler.py‎

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
TaskStatus,
8080
TaskStatusUpdateEvent,
8181
)
82+
from a2a.utils.constants import TERMINAL_TASK_STATES
8283
from a2a.utils.push_url_validator import (
8384
validate_push_notification_url,
8485
)
@@ -2700,14 +2701,6 @@ async def test_delete_task_push_notification_config_info_with_config_and_no_id(
27002701
assert len(result2.configs) == 0
27012702

27022703

2703-
TERMINAL_TASK_STATES = {
2704-
TaskState.TASK_STATE_COMPLETED,
2705-
TaskState.TASK_STATE_CANCELED,
2706-
TaskState.TASK_STATE_FAILED,
2707-
TaskState.TASK_STATE_REJECTED,
2708-
}
2709-
2710-
27112704
@pytest.mark.asyncio
27122705
@pytest.mark.parametrize('terminal_state', TERMINAL_TASK_STATES)
27132706
async def test_on_message_send_task_in_terminal_state(

‎tests/server/request_handlers/test_default_request_handler_v2.py‎

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
TaskStatus,
7474
TaskStatusUpdateEvent,
7575
)
76+
from a2a.utils.constants import TERMINAL_TASK_STATES
7677

7778

7879
def create_default_agent_card():
@@ -1189,14 +1190,6 @@ async def test_delete_task_push_notification_config_info_with_config_and_no_id()
11891190
assert len(result2.configs) == 0
11901191

11911192

1192-
TERMINAL_TASK_STATES = {
1193-
TaskState.TASK_STATE_COMPLETED,
1194-
TaskState.TASK_STATE_CANCELED,
1195-
TaskState.TASK_STATE_FAILED,
1196-
TaskState.TASK_STATE_REJECTED,
1197-
}
1198-
1199-
12001193
def create_input_mode_handler(*, validate_input_modes: bool):
12011194
"""A handler whose card declares text/plain as its only input mode."""
12021195
return DefaultRequestHandlerV2(

0 commit comments

Comments
 (0)