Skip to content

Materialize only input-required A2A status messages on both run paths - #116

Open
shibayan wants to merge 1 commit into
masterfrom
issue-99
Open

Materialize only input-required A2A status messages on both run paths#116
shibayan wants to merge 1 commit into
masterfrom
issue-99

Conversation

@shibayan

Copy link
Copy Markdown
Member

What this changes

Fixes #99. 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, while the status-update path did neither. The same remote task therefore produced
different response.messages depending only on how the caller consumed the run.

Both paths now derive their content from one helper: only an input-required status message
becomes a transcript message, and the history fallback is gone. Everything left out stays reachable
through rawRepresentation and the session's task state. Artifact conversion and the
streamed-artifact deduplication are untouched.

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.

Parity

  • Reference checked: .NET AgentTaskStatusExtensions, which returns content for
    TaskState.InputRequired alone and never reads task.history; its
    GetUserInputRequests_WithEmptyMessageParts_ReturnsNull case covers the empty-parts behaviour.
    Python internalizes a different subset on its status-update path and reads history[-1]; Go
    internalizes terminal status messages on both paths. The authority order selects .NET.
  • Wire format affected: no
  • Public API affected: no
  • Breaking change: yes — a terminal task whose only output was a status message or a history entry
    now folds to an empty response.

Checklist

  • pnpm check passes (lint, typecheck, build, test)
  • Behaviour changes are covered by a test that fails without the change
  • Public API changes are reflected in the package README and CHANGELOG.md

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>
Copilot AI lite review requested due to automatic review settings August 30, 2026 08:14
@shibayan shibayan added bug Usage: [PRs], Target: bug fixes and regressions; issues use the Bug issue type documentation Usage: [Issues, PRs], Target: documentation changes breaking change Usage: [PRs], Target: changes that are not backward compatible labels Aug 30, 2026
@github-actions github-actions Bot added a2a Usage: [Issues, PRs], Target: Agent2Agent protocol integrations breaking change Usage: [PRs], Target: changes that are not backward compatible and removed breaking change Usage: [PRs], Target: changes that are not backward compatible labels Aug 30, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 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.history fallback and stops surfacing terminal (completed/failed/canceled/rejected) and auth-required status 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

a2a Usage: [Issues, PRs], Target: Agent2Agent protocol integrations breaking change Usage: [PRs], Target: changes that are not backward compatible bug Usage: [PRs], Target: bug fixes and regressions; issues use the Bug issue type documentation Usage: [Issues, PRs], Target: documentation changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A2A terminal task conversion differs between run() and streaming and matches no reference implementation

2 participants