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

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

Ouverte
#2,708 0 commentaires 0 réactions 0 personnes assignées Voir sur GitHub
Langage dominant
Python
Étoiles
15.9k
Forks
1.4k
Merge moyen
1 j 17 h
PR mergées (30 j)
163

Description

### 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?

Guide de contribution

Ouvrir le guide de contribution

Évaluation

Cette issue n'a pas encore été évaluée.

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.