ag-ui-protocol / ag-ui-protocol/ag-ui

[Feature]: Restore A2UI surfaces from a MESSAGES_SNAPSHOT of durable tool results

未关闭
#2,708 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看
主要语言
Python
星标
15.9k
派生
1.4k
平均合并
1 天 17 小时
30 天内合并 PR
163

描述

### Pre-flight Checklist

- [x] I have searched existing issues and this hasn't been requested yet.

### Problem or Motivation

`A2UIMiddleware` paints surfaces as `ACTIVITY_SNAPSHOT` events while a run streams. Nothing durable records them: the backend stores the assistant tool call and the tool result, and `prepareRunAgentInput` strips activity from what the client sends back. When a client reconnects to a stored thread and the backend answers with a `MESSAGES_SNAPSHOT`, every A2UI surface is gone. The tool calls and results are there, but the middleware only ever produces activity from a live stream.

The same applies to a read-only load of a thread: a client wants to show the stored conversation, including its surfaces, without injecting tools, executing user actions or running recovery.

### Proposed Solution

A pure projector in `@ag-ui/a2ui-middleware` that rebuilds the `a2ui-surface` activity from the durable messages in a snapshot:

- `projectA2UIHistory(snapshot, config)` walks assistant tool calls and their tool results, and emits one activity per painted surface right after the assistant message that called for it. Multi-surface results become one activity per surface. A call without a result becomes a `building` card, an errored result a `failed` card, and the toolkit's recovery-exhausted envelope keeps its attempts.
- The surface is assembled with the catalog it was originally painted with. The middleware records the resolved catalog for that purpose: it forwards `toolResultMetadata` in `forwardedProps` when it injects the tool, so a backend that stores tool results can attach it, and live activity snapshots carry `A2UI_HISTORY_METADATA` with the owning tool call id. A result with no recorded catalog and no explicit `catalogId` argument fails closed with a failed card instead of guessing today's default.
- `A2UIMiddleware` applies the projector to every snapshot it forwards, and a `readOnly: true` option runs only the projection with nothing from the caller's input admitted.
- The projected snapshot declares `a2ui-surface` as its authoritative activity type so it never deletes activity that other producers still own on the client. That needs the client change proposed in #2707.
- The activity constants and `getOperationSurfaceId` move to a dependency free `./activity` module published as `@ag-ui/a2ui-middleware/activity`, so a browser renderer can share the contract without loading the Node-only streaming parser from the root entry.

### Alternatives Considered

- Persisting activity messages on the backend and sending them in the snapshot. This works only for backends that store AG-UI activity, and it duplicates the tool result the backend already stores.
- Replaying the recorded event stream instead of a snapshot. This keeps order but forces every reconnect through the full history and the middleware's live state machine.
- Assembling with the currently configured catalog. A surface restored with a different catalog than the one it was painted with renders wrong or not at all.

### Additional Context

I have a PR ready for this, stacked on the client change for #2707, and will open it once the issue is triaged. @ag-ui-protocol/copilotkit could you assign it to me?

贡献指南

打开贡献指南

评估

这个 Issue 还没有评估数据。

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。