Conversation
An awaited `run()` reads a whole task snapshot while a streamed one reads status-update events, and the two disagreed about what a task status contributes to the transcript. The snapshot path turned the status message of a `completed`, `failed`, `canceled` or `rejected` task into response content and fell back to the last agent message in `task.history` for a terminal task with no artifacts; the status-update path did neither. The same remote task therefore produced different `response.messages` depending only on how the caller consumed the run, and neither behaviour matched a reference implementation: .NET internalizes terminal status messages on neither path and never reads history, Python internalizes a different subset, Go internalizes on both. The authority order selects .NET, whose `AgentTaskStatusExtensions` returns content for `TaskState.InputRequired` alone. Both paths now derive their content from one `statusContents` helper, so only an `input-required` status message becomes a transcript message; the terminal states and the interrupted `auth-required` state stay out of it and remain reachable through `rawRepresentation` and the session's task state. The history fallback is gone. Sharing the helper also settles a second disagreement the split had hidden: an `input-required` status message carrying no parts named a message on the snapshot path but not on the streaming one, so folding a mixed stream could split an artifact around an empty message. It is now unnamed on both, matching .NET's empty-parts case. Artifact conversion and the streamed-artifact deduplication are untouched. Tests fold the awaited and streamed forms of one wire payload and compare whole transcripts rather than individual conversion helpers. Fixes #99 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
🟢 Approval recommended
Pull request overview
This PR fixes a parity bug in the A2A adapter where awaited run() (task snapshot conversion) and streamed run() (status/artifact event conversion) produced different folded transcripts for the same remote task. It unifies both paths so that only input-required status messages become transcript content, and removes the snapshot-only task.history fallback for terminal tasks—matching the .NET reference behavior described in #99.
Changes:
- Introduces a shared helper to materialize status message content only for
TASK_STATE_INPUT_REQUIRED, used by both snapshot and streamed conversion paths. - Removes the terminal-task
task.historyfallback and stops surfacing terminal (completed/failed/canceled/rejected) andauth-requiredstatus messages into the transcript. - Adds/updates tests and documentation (README + changelog + TSDoc) to lock in and explain the new transcript policy and awaited/streamed parity.
File summaries
| File | Description |
|---|---|
| packages/a2a/src/convert.ts | Centralizes status-message materialization rules and removes history fallback to make snapshot and streamed folding consistent. |
| packages/a2a/src/convert.test.ts | Updates unit tests to assert that non-input-required status messages and history are excluded from transcript content. |
| packages/a2a/src/agent.ts | Documents the transcript materialization policy and clarifies behavior for status/history vs artifacts. |
| packages/a2a/src/agent.test.ts | Adds end-to-end parity tests comparing fully folded awaited vs streamed transcripts for the same task shapes. |
| packages/a2a/README.md | Documents the “what becomes a response message” policy and notes the breaking behavior change. |
| CHANGELOG.md | Records the breaking change and explains the new unified rule and its parity motivation. |
Review details
- Files reviewed: 6/6 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this changes
Fixes #99. An awaited
run()reads a whole task snapshot while a streamed one reads status-updateevents, and the two disagreed about what a task status contributes to the transcript: the snapshot
path turned the status message of a
completed,failed,canceledorrejectedtask intoresponse content and fell back to the last agent message in
task.historyfor a terminal task withno artifacts, while the status-update path did neither. The same remote task therefore produced
different
response.messagesdepending only on how the caller consumed the run.Both paths now derive their content from one helper: only an
input-requiredstatus messagebecomes a transcript message, and the history fallback is gone. Everything left out stays reachable
through
rawRepresentationand the session's task state. Artifact conversion and thestreamed-artifact deduplication are untouched.
Sharing the helper also settles a second disagreement the split had hidden: an
input-requiredstatus message carrying no parts named a message on the snapshot path but not on the streaming one,
so folding a mixed stream could split an artifact around an empty message.
Parity
AgentTaskStatusExtensions, which returns content forTaskState.InputRequiredalone and never readstask.history; itsGetUserInputRequests_WithEmptyMessageParts_ReturnsNullcase covers the empty-parts behaviour.Python internalizes a different subset on its status-update path and reads
history[-1]; Gointernalizes terminal status messages on both paths. The authority order selects .NET.
now folds to an empty response.
Checklist
pnpm checkpasses (lint, typecheck, build, test)CHANGELOG.md