fix: Correctly set Append flag in TaskArtifactUpdateEvent and add UpdateArtifactAsync usage examples#120
fix: Correctly set Append flag in TaskArtifactUpdateEvent and add UpdateArtifactAsync usage examples#120
Conversation
Co-authored-by: brandonh-msft <20270743+brandonh-msft@users.noreply.github.com>
d12a2fa to
21ec564
Compare
|
@copilot try again |
…tifact regardless of user request Co-authored-by: brandonh-msft <20270743+brandonh-msft@users.noreply.github.com>
Head branch was pushed to by a user without write access
Fixed the Now the
Added a specific test case to cover this scenario. All 190 tests are passing. Commit: bf9ba4e |
| /// <param name="lastChunk">Whether this is the last chunk of the artifact.</param> | ||
| /// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param> | ||
| /// <returns>A task representing the asynchronous operation.</returns> | ||
| Task UpdateArtifactAsync(string taskId, Artifact artifact, bool append = false, bool? lastChunk = null, CancellationToken cancellationToken = default); |
There was a problem hiding this comment.
@copilot This method is not used yet (except in tests). Show me how it is supposed to be used on agent implementations in samples/ folder. This should play nice with the current ReturnArtifactAsync method implementation so observe how it is being used in samples/
There was a problem hiding this comment.
Enhanced the EchoAgentWithTasks sample to demonstrate how UpdateArtifactAsync should be used in agent implementations. The sample now shows three patterns:
- Simple responses:
ReturnArtifactAsyncfor complete, single artifacts (default behavior) - Streaming responses:
UpdateArtifactAsyncwith chunked data (usestream: <text>command) - Incremental building:
UpdateArtifactAsyncwith append functionality (useappend: <text>command)
The key difference from ReturnArtifactAsync is that UpdateArtifactAsync supports:
appendparameter to add to existing artifacts vs creating new oneslastChunkparameter for streaming scenarios- More granular control over artifact construction
This demonstrates the proper usage patterns alongside the existing ReturnArtifactAsync usage in the samples. Commit: 8a8af29
…examples in EchoAgentWithTasks Co-authored-by: Blackhex <23485+Blackhex@users.noreply.github.com>
…copilot/fix-21 # Conflicts: # src/A2A/A2A.csproj # tests/A2A.UnitTests/Server/TaskManagerTests.cs
…copilot/fix-21
| if (append && task.Artifacts.Count > 0) | ||
| { | ||
| // Append to the last artifact by adding parts to it | ||
| var lastArtifact = task.Artifacts[^1]; |
There was a problem hiding this comment.
Hello @brandonh-msft. Why are you taking the last artifact from the task ?
Is this a limitation from the A2A protocol, meaning we can only append to the last artifact from the task ?
If this is not a limitation, you could use the artifact object received from the method params and look for a corresponding artifactId ?
There was a problem hiding this comment.
Matching by artifactId is the correct approach here, not "last artifact".
The Artifact model already has ArtifactId as a required field. A task can have multiple artifacts concurrently — if an agent is streaming two separate outputs (say, a text artifact and a code artifact), using Last() would incorrectly append parts to whichever artifact was added most recently, regardless of which stream the chunk belongs to.
The fix should be:
var existing = task.Artifacts?.FirstOrDefault(a => a.ArtifactId == artifact.ArtifactId);
if (existing is not null)
{
existing.Parts.AddRange(artifact.Parts);
}
else
{
task.Artifacts ??= [];
task.Artifacts.Add(artifact);
}This way each artifact chunk is correctly routed to the artifact it belongs to, regardless of insertion order. The Append flag on the emitted event can then accurately reflect true when an existing artifact was found and updated, and false when a new one was created — which aligns with what Copilot already implemented for the event side.
|
Any news on this? It's IMHO an absolute must. |
|
Hi all, I'm interested in using the Is there any planned work to move this forward? If it would be helpful, I'd be happy to contribute - either by addressing the design feedback on this PR or by submitting a fresh implementation that incorporates the suggestions. Thanks for all the work on a2a-dotnet! |
Design decision: match by
|
Code reviewCritical
The implementation indexes into the last artifact unconditionally. This is incorrect when a task has multiple artifacts — chunks for artifact A would corrupt artifact B if artifact B happens to be last. The fix is to match by var existing = task.Artifacts.FirstOrDefault(a => a.ArtifactId == artifact.ArtifactId);HighAppend event sends the full merged artifact, not the chunk
When // Wrong: sends accumulated artifact
var taskUpdateEvent = new TaskArtifactUpdateEvent
{
Artifact = lastArtifact, // full merged content
Append = true,
...
};
// Correct: sends only the new chunk
var taskUpdateEvent = new TaskArtifactUpdateEvent
{
Artifact = artifact, // just the new parts
Append = true,
...
};The server-side accumulation in MediumCode duplication in the two branches The enumerator lookup and bool actuallyAppended = existing is not null;
var eventArtifact = actuallyAppended ? artifact : artifact; // same in both cases post-fix
// single event dispatch
var taskUpdateEvent = new TaskArtifactUpdateEvent
{
TaskId = task.Id,
Artifact = eventArtifact,
Append = actuallyAppended,
LastChunk = lastChunk
};XML doc still says "last artifact"
This will need updating once the Low
Minor inconsistency. The model allows Missing test: multiple concurrent artifacts There's no test covering the key scenario that motivates the // Task has artifact A (initial chunk) and artifact B (initial chunk)
// Append chunk to artifact A — verify artifact B is not modifiedwould lock in the correct behaviour and prevent regression. Missing cancellation test for Every other method has a SummaryThe structure of this PR is good — splitting
Everything else above is either documentation or test hygiene. |
|
Oops. I missed this PR. I implemented streaming artifacts in this PR #280 I believe my implementation addresses the two issues that @gijswalraven raised. |
|
This will be somehow implemented in v1 |
Pull request was closed
Fixes the
Appendproperty inTaskArtifactUpdateEventto accurately reflect what actually occurred during artifact updates, rather than simply echoing the user's request. Additionally adds comprehensive usage examples for theUpdateArtifactAsyncmethod in the samples.Problem
When calling
UpdateArtifactAsync(taskId, artifact, append: true)on a task with no existing artifacts, the system would:Append = truein the event (incorrect - misleading)This was confusing because the event indicated that appending occurred when actually a new artifact was created. Additionally, the
UpdateArtifactAsyncmethod lacked clear usage examples in the samples.Solution
The
Appendproperty inTaskArtifactUpdateEventnow reflects the actual operation performed:Append = truewhen parts were successfully appended to an existing artifactAppend = falsewhen a new artifact was created (regardless of what the user requested)Enhanced the
EchoAgentWithTaskssample to demonstrate properUpdateArtifactAsyncusage patterns alongside the existingReturnArtifactAsyncusage.Changes
UpdateArtifactAsyncto always setAppend = falsewhen creating new artifactsappend=trueis requested but no artifacts existEchoAgentWithTaskssample with three usage patterns:ReturnArtifactAsyncfor simple, complete responses (default behavior)UpdateArtifactAsyncwith streaming for chunked responses (usestream: <text>)UpdateArtifactAsyncwith append for building artifacts incrementally (useappend: <text>)A2A.csprojThis ensures that event consumers receive accurate information about what actually happened, while providing clear examples of how to use both artifact methods effectively.
Fixes #21.
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.