diff --git a/src/a2a/server/request_handlers/default_request_handler.py b/src/a2a/server/request_handlers/default_request_handler.py index fbd050a41..0d984b140 100644 --- a/src/a2a/server/request_handlers/default_request_handler.py +++ b/src/a2a/server/request_handlers/default_request_handler.py @@ -53,7 +53,6 @@ from a2a.utils.errors import ( ExtendedAgentCardNotConfiguredError, InternalError, - InvalidParamsError, PushNotificationNotSupportedError, TaskNotCancelableError, TaskNotFoundError, @@ -278,7 +277,7 @@ async def _setup_message_execution( if task: if task.status.state in TERMINAL_TASK_STATES: - raise InvalidParamsError( + raise UnsupportedOperationError( message=f'Task {task.id} is in terminal state: {task.status.state}' ) diff --git a/src/a2a/server/request_handlers/default_request_handler_v2.py b/src/a2a/server/request_handlers/default_request_handler_v2.py index 1b25e4f2b..aa79975e9 100644 --- a/src/a2a/server/request_handlers/default_request_handler_v2.py +++ b/src/a2a/server/request_handlers/default_request_handler_v2.py @@ -46,6 +46,7 @@ PushNotificationNotSupportedError, TaskNotCancelableError, TaskNotFoundError, + UnsupportedOperationError, ) from a2a.utils.task import ( apply_history_length, @@ -221,6 +222,10 @@ async def _setup_active_task( task = await self.task_store.get(original_task_id, call_context) if not task: raise TaskNotFoundError(f'Task {original_task_id} not found') + if task.status.state in TERMINAL_TASK_STATES: + raise UnsupportedOperationError( + message=f'Task {task.id} is in terminal state: {task.status.state}' + ) # Build context to resolve or generate missing IDs request_context = await self._request_context_builder.build( diff --git a/tests/integration/test_scenarios.py b/tests/integration/test_scenarios.py index a032323f9..f9ad9d9fb 100644 --- a/tests/integration/test_scenarios.py +++ b/tests/integration/test_scenarios.py @@ -53,6 +53,7 @@ InvalidParamsError, TaskNotCancelableError, TaskNotFoundError, + UnsupportedOperationError, ) @@ -428,8 +429,7 @@ async def cancel( role=Role.ROLE_USER, parts=[Part(text='message to completed task')], ) - # TODO: Is it correct error code ? - with pytest.raises(InvalidParamsError): + with pytest.raises(UnsupportedOperationError): async for _ in client.send_message(SendMessageRequest(message=msg2)): pass diff --git a/tests/server/request_handlers/test_default_request_handler.py b/tests/server/request_handlers/test_default_request_handler.py index 727679e7c..68a0a6fc4 100644 --- a/tests/server/request_handlers/test_default_request_handler.py +++ b/tests/server/request_handlers/test_default_request_handler.py @@ -2518,8 +2518,8 @@ async def test_delete_task_push_notification_config_info_with_config_and_no_id( @pytest.mark.asyncio @pytest.mark.parametrize('terminal_state', TERMINAL_TASK_STATES) async def test_on_message_send_task_in_terminal_state( - terminal_state, agent_card -): + terminal_state: TaskState, agent_card: AgentCard +) -> None: """Test on_message_send when task is already in a terminal state.""" state_name = TaskState.Name(terminal_state) task_id = f'terminal_task_{state_name}' @@ -2552,7 +2552,7 @@ async def test_on_message_send_task_in_terminal_state( 'a2a.server.request_handlers.default_request_handler.TaskManager.get_task', return_value=terminal_task, ): - with pytest.raises(InvalidParamsError) as exc_info: + with pytest.raises(UnsupportedOperationError) as exc_info: await request_handler.on_message_send( params, create_server_call_context() ) @@ -2566,8 +2566,8 @@ async def test_on_message_send_task_in_terminal_state( @pytest.mark.asyncio @pytest.mark.parametrize('terminal_state', TERMINAL_TASK_STATES) async def test_on_message_send_stream_task_in_terminal_state( - terminal_state, agent_card -): + terminal_state: TaskState, agent_card: AgentCard +) -> None: """Test on_message_send_stream when task is already in a terminal state.""" state_name = TaskState.Name(terminal_state) task_id = f'terminal_stream_task_{state_name}' @@ -2596,7 +2596,7 @@ async def test_on_message_send_stream_task_in_terminal_state( 'a2a.server.request_handlers.default_request_handler.TaskManager.get_task', return_value=terminal_task, ): - with pytest.raises(InvalidParamsError) as exc_info: + with pytest.raises(UnsupportedOperationError) as exc_info: async for _ in request_handler.on_message_send_stream( params, create_server_call_context() ): diff --git a/tests/server/request_handlers/test_default_request_handler_v2.py b/tests/server/request_handlers/test_default_request_handler_v2.py index 6011174e4..7f0ff4e78 100644 --- a/tests/server/request_handlers/test_default_request_handler_v2.py +++ b/tests/server/request_handlers/test_default_request_handler_v2.py @@ -39,6 +39,7 @@ InvalidParamsError, PushNotificationNotSupportedError, TaskNotFoundError, + UnsupportedOperationError, ) from a2a.types.a2a_pb2 import ( AgentCapabilities, @@ -961,7 +962,9 @@ async def test_delete_task_push_notification_config_info_with_config_and_no_id() @pytest.mark.asyncio @pytest.mark.parametrize('terminal_state', TERMINAL_TASK_STATES) -async def test_on_message_send_task_in_terminal_state(terminal_state): +async def test_on_message_send_task_in_terminal_state( + terminal_state: TaskState, +) -> None: """Test on_message_send when task is already in a terminal state.""" state_name = TaskState.Name(terminal_state) task_id = f'terminal_task_{state_name}' @@ -969,6 +972,7 @@ async def test_on_message_send_task_in_terminal_state(terminal_state): task_id=task_id, status_state=terminal_state ) mock_task_store = AsyncMock(spec=TaskStore) + mock_task_store.get.return_value = terminal_task request_handler = DefaultRequestHandlerV2( agent_executor=MockAgentExecutor(), task_store=mock_task_store, @@ -982,13 +986,7 @@ async def test_on_message_send_task_in_terminal_state(terminal_state): task_id=task_id, ) ) - with ( - patch( - 'a2a.server.request_handlers.default_request_handler.TaskManager.get_task', - return_value=terminal_task, - ), - pytest.raises(InvalidParamsError) as exc_info, - ): + with pytest.raises(UnsupportedOperationError) as exc_info: await request_handler.on_message_send( params, create_server_call_context() ) @@ -1000,7 +998,9 @@ async def test_on_message_send_task_in_terminal_state(terminal_state): @pytest.mark.asyncio @pytest.mark.parametrize('terminal_state', TERMINAL_TASK_STATES) -async def test_on_message_send_stream_task_in_terminal_state(terminal_state): +async def test_on_message_send_stream_task_in_terminal_state( + terminal_state: TaskState, +) -> None: """Test on_message_send_stream when task is already in a terminal state.""" state_name = TaskState.Name(terminal_state) task_id = f'terminal_stream_task_{state_name}' @@ -1008,6 +1008,7 @@ async def test_on_message_send_stream_task_in_terminal_state(terminal_state): task_id=task_id, status_state=terminal_state ) mock_task_store = AsyncMock(spec=TaskStore) + mock_task_store.get.return_value = terminal_task request_handler = DefaultRequestHandlerV2( agent_executor=MockAgentExecutor(), task_store=mock_task_store, @@ -1021,13 +1022,7 @@ async def test_on_message_send_stream_task_in_terminal_state(terminal_state): task_id=task_id, ) ) - with ( - patch( - 'a2a.server.request_handlers.default_request_handler.TaskManager.get_task', - return_value=terminal_task, - ), - pytest.raises(InvalidParamsError) as exc_info, - ): + with pytest.raises(UnsupportedOperationError) as exc_info: async for _ in request_handler.on_message_send_stream( params, create_server_call_context() ):