feat(ai-chat-ref): add undo action + button to demo agent

Wire a typed  action on the main `aiChat` agent in
references/ai-chat — `actionSchema` accepts `{ type: "undo" }` and
`onAction` calls `chat.history.slice(0, -2)` to drop the last
user/assistant exchange. Adds an Undo button to the chat input row
that calls `transport.sendAction(chatId, { type: "undo" })` and
optimistically updates local `useChat` state via `setMessages`.

Exercises the new TRI-9118 action semantics end-to-end through the
demo UI: clicking Undo emits a `chat action` span (not `chat turn`),
fires only `onAction()`, no `run()` / `streamText` / turn lifecycle
hooks. The next message turn sees the truncated server-side history.
This commit is contained in:
Eric Allam
2026-05-06 10:23:24 +01:00
parent 6585ce8e52
commit 080bace3f0
2 changed files with 33 additions and 0 deletions
@@ -937,6 +937,24 @@ export function Chat({
Stop
</button>
)}
{/* Undo — server-side `chat.history.slice(0, -2)` via the
`undo` action, optimistically reflected in the local
`useChat` state. Drops the last user / assistant exchange.
Only meaningful when there's at least one full exchange
and the chat isn't currently streaming. */}
{status !== "streaming" && messages.length >= 2 && (
<button
type="button"
disabled={isReadOnly}
onClick={() => {
void transport.sendAction(chatId, { type: "undo" });
setMessages((prev) => prev.slice(0, -2));
}}
className="rounded-lg bg-amber-500 px-4 py-2 text-sm font-medium text-white hover:bg-amber-600 disabled:opacity-50"
>
Undo
</button>
)}
</div>
</form>
</div>
+15
View File
@@ -401,6 +401,21 @@ export const aiChat = chat
},
// #endregion
// #region actionSchema + onAction — typed actions for state-only mutations
// Actions are not turns: only `hydrateMessages` and `onAction` fire,
// no `run()` invocation, no model call. The `undo` action drops the
// last user/assistant exchange so the next message turn sees a
// truncated history.
actionSchema: z.discriminatedUnion("type", [
z.object({ type: z.literal("undo") }),
]),
onAction: async ({ action }) => {
if (action.type === "undo") {
chat.history.slice(0, -2);
}
},
// #endregion
// #region onTurnComplete — persist + background self-review via chat.inject()
onTurnComplete: async ({
chatId,