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

[Feature] Client→server custom action channel (symmetric to server CUSTOM events)

Đang mở
#2,157 2 bình luận 0 reaction 0 người được giao Xem trên GitHub
enhancement proposal
Ngôn ngữ chính
Python
Star
15.9k
Fork
1.4k
Merge trung bình
1 ngày 17 giờ
Pull request đã merge (30 ngày)
163

Mô tả

## Summary

AG-UI has a **server→client** escape hatch — `CustomEvent` (`{ name, value }`) emitted from the agent's run stream — but there is **no client→server counterpart**. A UI cannot send an app-specific, out-of-band **action/signal** to the server without going through `RunAgentInput` (i.e. starting an agent run). There's no way to say *"the user just favorited message X"* or *"thumbs-up on this response"* without either invoking the model or bolting on a bespoke side-channel.

## Why this matters

### Many UI actions are out-of-band and must NOT trigger the LLM

Chat UIs are full of side actions that mutate app/session state but have nothing to do with a model turn:

- **Favorite / bookmark** a message
- **Thumbs up / down** (feedback) on a response
- **Pin** / **mark as read** / **archive** a message or conversation
- **Rename** or **close** a conversation

None of these should spin up an agent run, call the model, or append to the transcript. They're small, named, side-effecting signals from the client to the server.

### Today the client has only one channel, forcing bad trade-offs

The only client→server payload is `RunAgentInput` (`messages`, `state`, `context`, `forwardedProps`, `tools`). So an integrator must pick one of:

1. **Carry the action in `forwardedProps`/`state` on a run** — but that **invokes the run pipeline / the model** just to record a favorite, and pollutes the conversation. Wasteful and surprising.
2. **Build a bespoke side-channel** — a custom REST endpoint or a raw WebSocket frame the integrator invents. It works, but it's **off-spec**: every server and every client reinvents the shape, nothing is portable, and it defeats the point of a shared protocol.

### The asymmetry

The server can already push arbitrary named events to the client (`CUSTOM`), and integrations rely on it. The reverse — the client pushing an arbitrary named action to the server — simply doesn't exist. Making the protocol **symmetric** would close the gap once, instead of N bespoke channels.

## Concrete example: "favorite a message"

The user clicks the star on an assistant message. The UI wants to persist that as app/session state (e.g. a `favorite_keys` set keyed by message id) so it survives reloads and can be listed later. There is **no LLM involvement** — it's pure state.

Desired: the client emits something like

```json
{ "name": "favorite_add", "value": { "messageId": "msg_123" } }
```

and the server handles it (updates state, maybe emits a `STATE_DELTA` back) **without** starting a run. Today this has no home in AG-UI.

## Questions / possible directions (for discussion)

1. **A client→server custom action, symmetric to `CustomEvent`** — a lightweight, transport-agnostic `{ name, value }` "action"/"signal" the client can send **outside a run** (a small `POST`, or a frame on the persistent transport), which the server dispatches without invoking the agent. UIs get one portable mechanism for favorite/feedback/pin/etc.
2. **A documented `forwardedProps` action convention on a "no-op run"** — reserved keys + the semantics that such a run only applies the action and returns immediately (no model call). Cheaper spec-wise, but still rides the run pipeline.
3. **Bidirectional custom frames on stateful transports** — when a persistent connection exists (e.g. WebSocket), allow named client→server frames alongside the server→client `CUSTOM` events, so the channel is truly symmetric.

Each has trade-offs (portability vs. schema change vs. transport-specificity). I'd value a maintainer's take on the intended direction.

## Related

- #1915 — the *server-side* counterpart (agent emitting named `CustomEvent`s). This issue is the **opposite direction**: the *client* emitting a named action to the server.

## Offer

Happy to prototype the chosen shape end to end in the ADK middleware (`ag-ui-adk`) and `@ag-ui/client` — e.g. a `sendAction({name, value})` on the client that maps to a documented server handler which mutates state and can answer with a `STATE_DELTA`. Flagging it as design-first since option 1 touches the client API and (potentially) the transport contract.

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Đánh giá

Issue này chưa được đánh giá.

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.