a2aproject / a2aproject/a2a-rs
a2a-server: SSE streams have no event ids or keep-alive, and request bodies are unbounded
- Lingua principale
- Rust
- Stelle
- 75
- Fork
- 19
- Merge medio
- 11h 27m
- PR unite (30g)
- 21
Descrizione
## Summary
Three gaps in `a2a-server`'s HTTP surface, all present in `a2a-go` and absent here.
### 1. SSE events carry no `id`
`sse_from_stream` and `sse_jsonrpc_stream` both build events as `Event::default().data(...)`. There is no event identifier on the wire.
`a2a-go` writes one on every event (`internal/sse/sse.go:78`, `id:` prefix at `:34`). Without it a client cannot tell a gap from a clean sequence, and `Last-Event-ID` — the mechanism SSE defines for resuming an interrupted stream — has nothing to carry.
### 2. No keep-alive on SSE responses
Neither stream attaches a keep-alive, so an idle subscription sends nothing between events. Proxies and load balancers terminate idle connections, typically after 30–60s, which is well inside the lifetime of the long-running tasks `subscribe` exists for. `a2a-go` has `SSEWriter::WriteKeepAlive` (`internal/sse/sse.go:68`).
### 3. No request body size limit
Neither `rest_router` nor `jsonrpc_router` bounds the request body, so a single large POST is read into memory. `a2a-go` caps SSE payloads at `MaxSSETokenSize = 10 * 1024 * 1024` (`internal/sse/sse.go:39`); the other SDKs bound request bodies similarly.
## Why this matters to us specifically
`a2acli`'s `task subscribe` is the direct casualty of (1) and (2):
- **(2) is why a subscription dies.** A task that runs for minutes with quiet stretches gets its connection dropped by an intermediary, and the CLI sees a stream that simply ends.
- **(1) is why it cannot recover.** Tier 2 #186 requires resumption after disconnect, and `SPEC.md` §9.4 says to resume "from the last received event where the server supports it". Without event IDs our server does not support it, so the best `a2acli` can ever do against a2a-rs is re-subscribe from scratch.
So #186 is blocked on this, not the other way round.
## Scope
- [ ] Assign an `id` to every SSE event on both stream functions.
- [ ] Attach a keep-alive so idle subscriptions are not dropped by intermediaries.
- [ ] Bound request bodies on both routers, rejecting oversized ones with `413`.
- [ ] Tests: IDs present and increasing across multiple events on both functions; an over-limit body is rejected on both bindings.
## Note on the ID scheme
`a2a-go` uses a fresh UUID per event. A monotonic counter is the more useful choice: it lets a client detect that it missed event 4 between 3 and 5, which an opaque UUID cannot. Worth taking the counter and recording the divergence, rather than matching Go exactly.
## Credit
Found and first fixed by @ez-lbz in #125.
Guida per i contributori
Apri la guida per i contributori
Valutazione
Questa issue non è ancora stata valutata.