a2aproject / a2aproject/a2a-go
REST streaming stops at an unknown StreamResponse payload (v2.5.0)
- 主要言語
- Go
- スター
- 460
- フォーク
- 93
- 平均マージ
- 2日 21時間
- マージ済み PR(30日)
- 9
説明
Following [the request for SDK investigations in A2A #2129](https://github.com/a2aproject/A2A/pull/2129#discussion_r3805450070), here is a reproducible Go case for the already-reported unknown-event compatibility problem.
With **a2a-go v2.5.0**, a REST/SSE stream containing:
`Task → working status → unfamiliar payload → working status → completed status`
stops at the unfamiliar payload with `unknown stream response type`. The caller receives the first two events but neither later known update. The request explicitly sends `A2A-Version: 1.0`.
The synthetic `futureEvent` is valid JSON but is **not a defined A2A 1.0 event**. This demonstrates a constraint on future event additions; it does not assume the current specification already mandates accepting that variant.
### Reproduction
With Go 1.25+, save this as `main.go` in a fresh directory:
Self-contained loopback reproduction
```go
package main
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"time"
"github.com/a2aproject/a2a-go/v2/a2a"
"github.com/a2aproject/a2a-go/v2/a2aclient"
)
func main() {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/event-stream")
if _, err := fmt.Fprint(w, `data: {"task":{"id":"task-fixture","contextId":"context-fixture","status":{"state":"TASK_STATE_SUBMITTED"}}}
data: {"statusUpdate":{"taskId":"task-fixture","contextId":"context-fixture","status":{"state":"TASK_STATE_WORKING"}}}
data: {"futureEvent":{"taskId":"task-fixture","contextId":"context-fixture","text":"synthetic future event; not a defined A2A 1.0 variant"}}
data: {"statusUpdate":{"taskId":"task-fixture","contextId":"context-fixture","status":{"state":"TASK_STATE_WORKING"}}}
data: {"statusUpdate":{"taskId":"task-fixture","contextId":"context-fixture","status":{"state":"TASK_STATE_COMPLETED"}}}
`); err != nil {
panic(err)
}
}))
defer server.Close()
endpoint, err := url.Parse(server.URL)
if err != nil {
panic(err)
}
transport := a2aclient.NewRESTTransport(endpoint, &http.Client{Timeout: 5 * time.Second})
message := a2a.NewMessage(a2a.MessageRoleUser, a2a.NewTextPart("fixture request"))
message.ID = "message-fixture"
count := 0
for event, err := range transport.SendStreamingMessage(context.Background(), a2aclient.ServiceParams{"A2A-Version": {"1.0"}}, &a2a.SendMessageRequest{Message: message}) {
if err != nil {
fmt.Printf("after %d events: %v\n", count, err)
return
}
count++
fmt.Printf("event %d: %T\n", count, event)
}
}
```
```sh
go mod init example.com/a2a-unknown-event-repro
go get github.com/a2aproject/a2a-go/v2@v2.5.0
go mod tidy
go run .
```
Observed with the published module, without local SDK changes:
```text
event 1: *a2a.Task
event 2: *a2a.TaskStatusUpdateEvent
after 2 events: unknown stream response type
```
### Controls
Using the same public REST receive path and loopback fixture:
- Known events only, or an extra field inside a known status update: all four known events arrive, including completion.
- A malformed known update (`status: 42`) or two recognized payload members in one wrapper: parsing fails.
- An HTTP body shorter than its declared Content-Length: `SSE stream error: unexpected EOF`; no completion is delivered.
- An empty `{}` wrapper also produces `unknown stream response type`.
For comparison, Python a2a-sdk 1.1.3 stops at the same unfamiliar payload with a protobuf `ParseError`. Only HTTP+JSON/SSE was exercised; this report makes no claim about other bindings or high-level aggregation.
### Proposed next step
Would you prefer a narrow change that skips unfamiliar payloads with a diagnostic and continues to known events, an opaque-event representation, or keeping rejection with an explicit version-negotiation requirement? The handling should distinguish the intended future-event case from empty/malformed responses and keep real transport failures visible.
[Spec §5.7](https://github.com/a2aproject/A2A/blob/98853be376c88df25e1704771cd3ea9ef8823a96/docs/specification.md#57-field-presence-and-optionality) recommends ignoring unknown fields, but does not explicitly define the receive API behavior when no recognized payload remains. I can contribute the focused implementation and regression cases once that behavior is agreed.
Tested Go release: `9d95b95445f4208ba77f48a137a278067937adb7`. The relevant REST parser and transport are unchanged at inspected main `03b1f8483cc9cbdd3e95567100d95c5509687eb5` (static comparison). Existing `go test ./a2aclient ./internal/rest` checks pass.
コントリビューションガイド
調査の方向性
The issue is in the REST/SSE stream parsing logic when encountering an unknown event type. Start by examining the transport layer, likely in `a2aclient/rest_transport.go` or the internal REST parser. Look for the error 'unknown stream response type'. The reproduction code provides a concrete test case; run it to see the failure. The fix involves modifying the event decoding to skip unknown payloads while continuing the stream, ensuring known subsequent events are still delivered. Check existing tests in `a2aclient` and `internal/rest` for guidance on adding a regression test.
索引モデルが issue の本文から書いたものです。
評価
- 技術スタック
- go
- 領域
- api, backend
- issue の種類
- バグ
- 難易度
- 3/5
- 見積もり時間
- 1〜2日
- 活発さ
- 活発
- 明瞭さ
- 明確に書かれている
- 初心者へのやさしさ
- 65/100