Skip to content

Commit 4eb252c

Browse files
committed
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.
1 parent e1fb45d commit 4eb252c

2 files changed

Lines changed: 33 additions & 0 deletions

File tree

references/ai-chat/src/components/chat.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -937,6 +937,24 @@ export function Chat({
937937
Stop
938938
</button>
939939
)}
940+
{/* Undo — server-side `chat.history.slice(0, -2)` via the
941+
`undo` action, optimistically reflected in the local
942+
`useChat` state. Drops the last user / assistant exchange.
943+
Only meaningful when there's at least one full exchange
944+
and the chat isn't currently streaming. */}
945+
{status !== "streaming" && messages.length >= 2 && (
946+
<button
947+
type="button"
948+
disabled={isReadOnly}
949+
onClick={() => {
950+
void transport.sendAction(chatId, { type: "undo" });
951+
setMessages((prev) => prev.slice(0, -2));
952+
}}
953+
className="rounded-lg bg-amber-500 px-4 py-2 text-sm font-medium text-white hover:bg-amber-600 disabled:opacity-50"
954+
>
955+
Undo
956+
</button>
957+
)}
940958
</div>
941959
</form>
942960
</div>

references/ai-chat/src/trigger/chat.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,21 @@ export const aiChat = chat
401401
},
402402
// #endregion
403403

404+
// #region actionSchema + onAction — typed actions for state-only mutations
405+
// Actions are not turns: only `hydrateMessages` and `onAction` fire,
406+
// no `run()` invocation, no model call. The `undo` action drops the
407+
// last user/assistant exchange so the next message turn sees a
408+
// truncated history.
409+
actionSchema: z.discriminatedUnion("type", [
410+
z.object({ type: z.literal("undo") }),
411+
]),
412+
onAction: async ({ action }) => {
413+
if (action.type === "undo") {
414+
chat.history.slice(0, -2);
415+
}
416+
},
417+
// #endregion
418+
404419
// #region onTurnComplete — persist + background self-review via chat.inject()
405420
onTurnComplete: async ({
406421
chatId,

0 commit comments

Comments
 (0)