diff --git a/src/a2a/server/tasks/task_manager.py b/src/a2a/server/tasks/task_manager.py index e5d899c1e..c9dfc879f 100644 --- a/src/a2a/server/tasks/task_manager.py +++ b/src/a2a/server/tasks/task_manager.py @@ -12,7 +12,7 @@ TaskStatus, TaskStatusUpdateEvent, ) -from a2a.utils.errors import InvalidParamsError +from a2a.utils.errors import InvalidAgentResponseError, InvalidParamsError from a2a.utils.telemetry import trace_function @@ -74,12 +74,16 @@ def append_artifact_to_task(task: Task, event: TaskArtifactUpdateEvent) -> None: dict(new_artifact_data.metadata.items()) ) else: - # We received a chunk to append, but we don't have an existing artifact. - # we will ignore this chunk - logger.warning( - 'Received append=True for nonexistent artifact index %s in task %s. Ignoring chunk.', - artifact_id, - task.id, + # A2A spec (Section 4.2.2 TaskArtifactUpdateEvent, + # https://a2a-protocol.org/v1.0.0/specification/#422-taskartifactupdateevent): + # "append — If true, the content of this artifact should be + # appended to a previously sent artifact." + # append=True with no prior artifact of this ID is an agent + # protocol violation (#1038). + raise InvalidAgentResponseError( + f'append=True for nonexistent artifact_id={artifact_id!r} ' + f'in task {task.id!r}. The artifact must be created (append=False) ' + f'before appending parts to it.' ) diff --git a/tests/server/tasks/test_task_manager.py b/tests/server/tasks/test_task_manager.py index eba8d2f14..7daa3ab3d 100644 --- a/tests/server/tasks/test_task_manager.py +++ b/tests/server/tasks/test_task_manager.py @@ -18,7 +18,7 @@ TaskStatus, TaskStatusUpdateEvent, ) -from a2a.utils.errors import InvalidParamsError +from a2a.utils.errors import InvalidAgentResponseError, InvalidParamsError class SampleUser(User): @@ -429,6 +429,7 @@ def test_append_artifact_to_task(): assert len(task.artifacts[1].parts) == 1 # Test appending part to a task that does not have a matching artifact + # should raise InvalidAgentResponseError instead of silently dropping (#1038) non_existing_artifact_with_parts = Artifact( artifact_id='artifact-456', parts=[Part(text='Part 1')] ) @@ -438,7 +439,8 @@ def test_append_artifact_to_task(): task_id='123', context_id='123', ) - append_artifact_to_task(task, append_event_5) - assert len(task.artifacts) == 2 - assert len(task.artifacts[0].parts) == 2 - assert len(task.artifacts[1].parts) == 1 + with pytest.raises( + InvalidAgentResponseError, + match='append=True for nonexistent artifact_id', + ): + append_artifact_to_task(task, append_event_5)