@assistant-ui/mcp-docs-server 0.1.27 → 0.1.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/.docs/organized/code-examples/waterfall.md +6 -6
  2. package/.docs/organized/code-examples/with-a2a.md +7 -7
  3. package/.docs/organized/code-examples/with-ag-ui.md +8 -8
  4. package/.docs/organized/code-examples/with-ai-sdk-v6.md +9 -9
  5. package/.docs/organized/code-examples/with-artifacts.md +9 -9
  6. package/.docs/organized/code-examples/with-assistant-transport.md +7 -7
  7. package/.docs/organized/code-examples/with-chain-of-thought.md +9 -9
  8. package/.docs/organized/code-examples/with-cloud-standalone.md +9 -9
  9. package/.docs/organized/code-examples/with-cloud.md +9 -9
  10. package/.docs/organized/code-examples/with-custom-thread-list.md +9 -9
  11. package/.docs/organized/code-examples/with-elevenlabs-conversational.md +11 -11
  12. package/.docs/organized/code-examples/with-elevenlabs-scribe.md +11 -11
  13. package/.docs/organized/code-examples/with-expo.md +17 -17
  14. package/.docs/organized/code-examples/with-external-store.md +7 -7
  15. package/.docs/organized/code-examples/with-ffmpeg.md +9 -9
  16. package/.docs/organized/code-examples/with-generative-ui.md +841 -0
  17. package/.docs/organized/code-examples/with-google-adk.md +7 -7
  18. package/.docs/organized/code-examples/with-heat-graph.md +6 -6
  19. package/.docs/organized/code-examples/with-interactables.md +9 -9
  20. package/.docs/organized/code-examples/with-langgraph.md +9 -9
  21. package/.docs/organized/code-examples/with-livekit.md +50 -14
  22. package/.docs/organized/code-examples/with-opencode.md +2394 -0
  23. package/.docs/organized/code-examples/with-parent-id-grouping.md +8 -8
  24. package/.docs/organized/code-examples/with-react-hook-form.md +10 -10
  25. package/.docs/organized/code-examples/with-react-ink.md +3 -3
  26. package/.docs/organized/code-examples/with-react-router.md +8 -8
  27. package/.docs/organized/code-examples/with-store.md +6 -6
  28. package/.docs/organized/code-examples/with-tanstack.md +10 -10
  29. package/.docs/organized/code-examples/with-tap-runtime.md +7 -7
  30. package/.docs/raw/docs/(docs)/guides/mentions.mdx +406 -0
  31. package/.docs/raw/docs/(docs)/guides/slash-commands.mdx +275 -0
  32. package/.docs/raw/docs/(docs)/guides/voice.mdx +10 -3
  33. package/.docs/raw/docs/primitives/composer.mdx +27 -4
  34. package/.docs/raw/docs/runtimes/custom/custom-thread-list.mdx +2 -2
  35. package/.docs/raw/docs/runtimes/custom/external-store.mdx +38 -0
  36. package/.docs/raw/docs/runtimes/custom/local.mdx +65 -18
  37. package/.docs/raw/docs/runtimes/google-adk/index.mdx +62 -0
  38. package/.docs/raw/docs/runtimes/langgraph/index.mdx +137 -1
  39. package/package.json +4 -4
@@ -0,0 +1,275 @@
1
+ ---
2
+ title: Slash Commands
3
+ description: Let users type / in the composer to trigger predefined actions from a popover picker.
4
+ ---
5
+
6
+ Slash commands let users type `/` in the composer to open a popover, browse available commands, and execute one. Unlike [mentions](/docs/guides/mentions) (which insert text into the message), slash commands trigger **actions** — the `/command` text is removed from the composer and a callback fires.
7
+
8
+ ## How It Works
9
+
10
+ ```
11
+ User types "/" → Trigger detected → Adapter provides commands
12
+ ↓
13
+ Command executed ← User selects command from popover
14
+ ↓
15
+ "/command" text removed from composer
16
+ ```
17
+
18
+ The slash command system is built on the same [trigger popover architecture](#trigger-popover-architecture) as mentions. It has two layers:
19
+
20
+ 1. **Adapter** — provides the list of available commands (flat list by default, or categorized for advanced use)
21
+ 2. **SlashCommandRoot** — a convenience wrapper that pre-configures the trigger character (`/`) and action-based select behavior
22
+
23
+ ## Quick Start
24
+
25
+ ### 1. Define Commands
26
+
27
+ ```tsx
28
+ import {
29
+ unstable_useSlashCommandAdapter,
30
+ ComposerPrimitive,
31
+ } from "@assistant-ui/react";
32
+
33
+ // Define commands outside the component for a stable reference
34
+ const COMMANDS = [
35
+ {
36
+ name: "summarize",
37
+ description: "Summarize the conversation",
38
+ execute: () => console.log("Summarize!"),
39
+ },
40
+ {
41
+ name: "translate",
42
+ description: "Translate text to another language",
43
+ execute: () => console.log("Translate!"),
44
+ },
45
+ {
46
+ name: "help",
47
+ description: "List all available commands",
48
+ },
49
+ ];
50
+
51
+ function MyComposer() {
52
+ const slashAdapter = unstable_useSlashCommandAdapter({
53
+ commands: COMMANDS,
54
+ });
55
+
56
+ return (
57
+ <ComposerPrimitive.Unstable_SlashCommandRoot adapter={slashAdapter}>
58
+ <ComposerPrimitive.Root>
59
+ <ComposerPrimitive.Input placeholder="Type / for commands..." />
60
+ <ComposerPrimitive.Send>Send</ComposerPrimitive.Send>
61
+
62
+ <ComposerPrimitive.Unstable_TriggerPopoverPopover className="popover">
63
+ <ComposerPrimitive.Unstable_TriggerPopoverItems>
64
+ {(items) =>
65
+ items.map((item, index) => (
66
+ <ComposerPrimitive.Unstable_TriggerPopoverItem
67
+ key={item.id}
68
+ item={item}
69
+ index={index}
70
+ className="popover-item"
71
+ >
72
+ <strong>{item.label}</strong>
73
+ {item.description && <span>{item.description}</span>}
74
+ </ComposerPrimitive.Unstable_TriggerPopoverItem>
75
+ ))
76
+ }
77
+ </ComposerPrimitive.Unstable_TriggerPopoverItems>
78
+ </ComposerPrimitive.Unstable_TriggerPopoverPopover>
79
+ </ComposerPrimitive.Root>
80
+ </ComposerPrimitive.Unstable_SlashCommandRoot>
81
+ );
82
+ }
83
+ ```
84
+
85
+ ### 2. Handle Command Selection
86
+
87
+ There are two ways to handle command execution:
88
+
89
+ **Via `execute` on each item** — define the action inline in the command definition:
90
+
91
+ ```ts
92
+ { name: "summarize", execute: () => runSummarize() }
93
+ ```
94
+
95
+ **Via `onSelect` prop** — handle all commands in one place:
96
+
97
+ ```tsx
98
+ <ComposerPrimitive.Unstable_SlashCommandRoot
99
+ adapter={slashAdapter}
100
+ onSelect={(item) => {
101
+ switch (item.id) {
102
+ case "summarize": runSummarize(); break;
103
+ case "translate": runTranslate(); break;
104
+ }
105
+ }}
106
+ >
107
+ ```
108
+
109
+ Both `execute` and `onSelect` fire when a command is selected. Use whichever pattern fits your code.
110
+
111
+ ## Custom Adapter
112
+
113
+ The `unstable_useSlashCommandAdapter` hook uses a **flat list** — all commands show immediately when `/` is typed, with search filtering as the user types. This is the recommended UX for most cases.
114
+
115
+ For **categorized navigation** (drill-down into groups), build the adapter manually. Return categories from `categories()` and items from `categoryItems()`. The popover will show categories first, then items within the selected category:
116
+
117
+ ```ts
118
+ import type { Unstable_SlashCommandAdapter } from "@assistant-ui/core";
119
+
120
+ const adapter: Unstable_SlashCommandAdapter = {
121
+ categories() {
122
+ return [
123
+ { id: "actions", label: "Actions" },
124
+ { id: "export", label: "Export" },
125
+ ];
126
+ },
127
+
128
+ categoryItems(categoryId) {
129
+ if (categoryId === "actions") {
130
+ return [
131
+ { id: "summarize", type: "command", label: "/summarize", description: "Summarize the conversation" },
132
+ { id: "translate", type: "command", label: "/translate", description: "Translate text" },
133
+ ];
134
+ }
135
+ if (categoryId === "export") {
136
+ return [
137
+ { id: "pdf", type: "command", label: "/export pdf", description: "Export as PDF" },
138
+ { id: "markdown", type: "command", label: "/export md", description: "Export as Markdown" },
139
+ ];
140
+ }
141
+ return [];
142
+ },
143
+
144
+ // Optional — enables search across all categories
145
+ search(query) {
146
+ const lower = query.toLowerCase();
147
+ const all = [...this.categoryItems("actions"), ...this.categoryItems("export")];
148
+ return all.filter(
149
+ (item) => item.label.toLowerCase().includes(lower) || item.description?.toLowerCase().includes(lower),
150
+ );
151
+ },
152
+ };
153
+ ```
154
+
155
+ When using a categorized adapter, add `TriggerPopoverCategories` to your popover UI:
156
+
157
+ ```tsx
158
+ <ComposerPrimitive.Unstable_TriggerPopoverPopover>
159
+ <ComposerPrimitive.Unstable_TriggerPopoverBack>← Back</ComposerPrimitive.Unstable_TriggerPopoverBack>
160
+ <ComposerPrimitive.Unstable_TriggerPopoverCategories>
161
+ {(categories) => categories.map((cat) => (
162
+ <ComposerPrimitive.Unstable_TriggerPopoverCategoryItem key={cat.id} categoryId={cat.id}>
163
+ {cat.label}
164
+ </ComposerPrimitive.Unstable_TriggerPopoverCategoryItem>
165
+ ))}
166
+ </ComposerPrimitive.Unstable_TriggerPopoverCategories>
167
+ <ComposerPrimitive.Unstable_TriggerPopoverItems>
168
+ {(items) => items.map((item, index) => (
169
+ <ComposerPrimitive.Unstable_TriggerPopoverItem key={item.id} item={item} index={index}>
170
+ {item.label}
171
+ </ComposerPrimitive.Unstable_TriggerPopoverItem>
172
+ ))}
173
+ </ComposerPrimitive.Unstable_TriggerPopoverItems>
174
+ </ComposerPrimitive.Unstable_TriggerPopoverPopover>
175
+ ```
176
+
177
+ ## Combining with Mentions
178
+
179
+ Slash commands and mentions can coexist on the same composer. Nest both roots — the [plugin protocol](#trigger-popover-architecture) ensures they don't conflict:
180
+
181
+ ```tsx
182
+ <ComposerPrimitive.Unstable_MentionRoot adapter={mentionAdapter}>
183
+ <ComposerPrimitive.Unstable_SlashCommandRoot adapter={slashAdapter}>
184
+ <ComposerPrimitive.Root>
185
+ <ComposerPrimitive.Input placeholder="Type @ to mention, / for commands..." />
186
+ <ComposerPrimitive.Send>Send</ComposerPrimitive.Send>
187
+
188
+ {/* Mention popover — shows when @ is typed */}
189
+ <ComposerPrimitive.Unstable_MentionPopover>
190
+ <ComposerPrimitive.Unstable_MentionCategories>
191
+ {(categories) => categories.map((cat) => (
192
+ <ComposerPrimitive.Unstable_MentionCategoryItem key={cat.id} categoryId={cat.id}>
193
+ {cat.label}
194
+ </ComposerPrimitive.Unstable_MentionCategoryItem>
195
+ ))}
196
+ </ComposerPrimitive.Unstable_MentionCategories>
197
+ <ComposerPrimitive.Unstable_MentionItems>
198
+ {(items) => items.map((item) => (
199
+ <ComposerPrimitive.Unstable_MentionItem key={item.id} item={item}>
200
+ {item.label}
201
+ </ComposerPrimitive.Unstable_MentionItem>
202
+ ))}
203
+ </ComposerPrimitive.Unstable_MentionItems>
204
+ </ComposerPrimitive.Unstable_MentionPopover>
205
+
206
+ {/* Slash command popover — shows when / is typed */}
207
+ <ComposerPrimitive.Unstable_TriggerPopoverPopover>
208
+ <ComposerPrimitive.Unstable_TriggerPopoverItems>
209
+ {(items) => items.map((item, index) => (
210
+ <ComposerPrimitive.Unstable_TriggerPopoverItem key={item.id} item={item} index={index}>
211
+ {item.label}
212
+ </ComposerPrimitive.Unstable_TriggerPopoverItem>
213
+ ))}
214
+ </ComposerPrimitive.Unstable_TriggerPopoverItems>
215
+ </ComposerPrimitive.Unstable_TriggerPopoverPopover>
216
+ </ComposerPrimitive.Root>
217
+ </ComposerPrimitive.Unstable_SlashCommandRoot>
218
+ </ComposerPrimitive.Unstable_MentionRoot>
219
+ ```
220
+
221
+ Each root provides its own `TriggerPopoverContext`. When the user types `@`, the mention popover opens. When they type `/`, the slash command popover opens. Keyboard events route to whichever popover is active.
222
+
223
+ ## Keyboard Navigation
224
+
225
+ Same keyboard bindings as mentions:
226
+
227
+ | Key | Action |
228
+ | --- | --- |
229
+ | <Kbd>ArrowDown</Kbd> | Highlight next item |
230
+ | <Kbd>ArrowUp</Kbd> | Highlight previous item |
231
+ | <Kbd>Enter</Kbd> | Execute highlighted command / drill into category |
232
+ | <Kbd>Escape</Kbd> | Close popover |
233
+ | <Kbd>Backspace</Kbd> | Go back to categories (when query is empty) |
234
+
235
+ ## Trigger Popover Architecture
236
+
237
+ Both mentions and slash commands are built on a generic **trigger popover** system:
238
+
239
+ - `ComposerPrimitive.Unstable_TriggerPopoverRoot` — the generic root, parameterized by trigger character and select behavior
240
+ - `ComposerPrimitive.Unstable_MentionRoot` — preset with `trigger="@"` and `onSelect: insertDirective`
241
+ - `ComposerPrimitive.Unstable_SlashCommandRoot` — preset with `trigger="/"` and `onSelect: action`
242
+
243
+ The trigger popover primitives (`TriggerPopoverPopover`, `TriggerPopoverItems`, etc.) are shared across both. You can also use `TriggerPopoverRoot` directly to build custom trigger systems with other characters (e.g. `:` for emoji).
244
+
245
+ ### ComposerInput Plugin Protocol
246
+
247
+ Under the hood, each trigger root registers a **ComposerInputPlugin** with the composer input. This is a generic protocol that decouples the input from any specific trigger:
248
+
249
+ ```ts
250
+ type ComposerInputPlugin = {
251
+ handleKeyDown(e: KeyboardEvent): boolean;
252
+ setCursorPosition(pos: number): void;
253
+ };
254
+ ```
255
+
256
+ The input iterates over registered plugins for keyboard events and cursor changes. This is what enables multiple trigger roots to coexist without conflict.
257
+
258
+ ## Primitives Reference
259
+
260
+ | Primitive | Description |
261
+ | --- | --- |
262
+ | `Unstable_SlashCommandRoot` | Convenience wrapper — `TriggerPopoverRoot` with `trigger="/"` and action behavior |
263
+ | `Unstable_TriggerPopoverRoot` | Generic root — configurable trigger character and select behavior |
264
+ | `Unstable_TriggerPopoverPopover` | Container — only renders when a trigger is active (`role="listbox"`) |
265
+ | `Unstable_TriggerPopoverCategories` | Render-function for the top-level category list |
266
+ | `Unstable_TriggerPopoverCategoryItem` | Button that drills into a category (`role="option"`, auto `data-highlighted`) |
267
+ | `Unstable_TriggerPopoverItems` | Render-function for items within the active category or search results |
268
+ | `Unstable_TriggerPopoverItem` | Button that selects an item (`role="option"`, auto `data-highlighted`) |
269
+ | `Unstable_TriggerPopoverBack` | Button that navigates back from items to categories |
270
+
271
+ ## Related
272
+
273
+ - [Mentions Guide](/docs/guides/mentions) — `@`-mention system built on the same architecture
274
+ - [Suggestions Guide](/docs/guides/suggestions) — static follow-up prompts (different from slash commands)
275
+ - [Composer Primitives](/docs/primitives/composer) — underlying composer primitives
@@ -303,14 +303,21 @@ const runtime = useChatRuntime({
303
303
 
304
304
  ## Example: LiveKit
305
305
 
306
- [LiveKit](https://livekit.io/) provides realtime voice via WebRTC rooms with transcription support.
306
+ [LiveKit](https://livekit.io/) provides realtime voice via WebRTC rooms with transcription support. Unlike fully-hosted agent services, LiveKit follows a "bring-your-own-agent" model: the browser adapter only joins a room, and you run a separate **agent worker** that joins the same room and handles STT, LLM, and TTS. Without an agent in the room, the client will connect successfully but have nothing to talk to.
307
+
308
+ ### Prerequisites
309
+
310
+ 1. **A LiveKit server** — create a [LiveKit Cloud](https://cloud.livekit.io/) project (grab the URL, API Key, and API Secret from the project settings) or self-host `livekit-server`.
311
+ 2. **An agent worker** — built with the [LiveKit Agents SDK](https://docs.livekit.io/agents/) (Python or Node). The worker connects to your LiveKit server and is automatically dispatched into new rooms.
307
312
 
308
313
  ### Install Dependencies
309
314
 
310
315
  ```bash
311
- npm install livekit-client
316
+ npm install livekit-client livekit-server-sdk
312
317
  ```
313
318
 
319
+ `livekit-client` powers the browser adapter; `livekit-server-sdk` is used server-side to mint access tokens.
320
+
314
321
  ### Usage
315
322
 
316
323
  ```tsx
@@ -330,4 +337,4 @@ const runtime = useChatRuntime({
330
337
  });
331
338
  ```
332
339
 
333
- See the `examples/with-livekit` directory in the repository for a complete implementation including the adapter and token endpoint.
340
+ See the [`with-livekit` example](https://github.com/assistant-ui/assistant-ui/tree/main/examples/with-livekit) for a complete implementation: the browser adapter, the token endpoint, and a minimal Python agent worker using the OpenAI Realtime API.
@@ -102,19 +102,42 @@ import { ComposerPrimitive } from "@assistant-ui/react";
102
102
 
103
103
  The primitive's behavior (keyboard handling, disabled state, form submission) is merged onto your element. Your styles, your component, primitive wiring.
104
104
 
105
- ### Unstable Mentions
105
+ ### Unstable Trigger Popovers
106
106
 
107
- Composer also includes an unstable mention system for `@`-triggered popovers. It is built around `ComposerPrimitive.Unstable_MentionRoot` and intended for rich inputs like `LexicalComposerInput`.
107
+ Composer includes an unstable **trigger popover** system for character-triggered popovers (e.g. `@` for mentions, `/` for slash commands). Multiple triggers can coexist on the same input.
108
+
109
+ **Mentions** (`@` trigger) — insert directive text into the message:
108
110
 
109
111
  ```tsx
110
- <ComposerPrimitive.Unstable_MentionRoot>
112
+ <ComposerPrimitive.Unstable_MentionRoot adapter={mentionAdapter}>
111
113
  <ComposerPrimitive.Root>
112
- <LexicalComposerInput placeholder="Type @ to mention a tool..." />
114
+ <ComposerPrimitive.Input placeholder="Type @ to mention..." />
113
115
  <ComposerPrimitive.Unstable_MentionPopover />
114
116
  </ComposerPrimitive.Root>
115
117
  </ComposerPrimitive.Unstable_MentionRoot>
116
118
  ```
117
119
 
120
+ **Slash commands** (`/` trigger) — execute an action and clear the command text:
121
+
122
+ ```tsx
123
+ <ComposerPrimitive.Unstable_SlashCommandRoot adapter={slashAdapter}>
124
+ <ComposerPrimitive.Root>
125
+ <ComposerPrimitive.Input placeholder="Type / for commands..." />
126
+ <ComposerPrimitive.Unstable_TriggerPopoverPopover>
127
+ <ComposerPrimitive.Unstable_TriggerPopoverItems>
128
+ {(items) => items.map(item => (
129
+ <ComposerPrimitive.Unstable_TriggerPopoverItem key={item.id} item={item}>
130
+ {item.label}
131
+ </ComposerPrimitive.Unstable_TriggerPopoverItem>
132
+ ))}
133
+ </ComposerPrimitive.Unstable_TriggerPopoverItems>
134
+ </ComposerPrimitive.Unstable_TriggerPopoverPopover>
135
+ </ComposerPrimitive.Root>
136
+ </ComposerPrimitive.Unstable_SlashCommandRoot>
137
+ ```
138
+
139
+ See the [Mentions guide](/docs/guides/mentions) and [Slash Commands guide](/docs/guides/slash-commands) for full documentation.
140
+
118
141
  ## Parts
119
142
 
120
143
  ### Root
@@ -238,12 +238,12 @@ const aui = useAui();
238
238
 
239
239
  const history = useMemo<ThreadHistoryAdapter>(
240
240
  () => ({
241
- async append(message) {
241
+ async append({ message, parentId }) {
242
242
  // Wait for initialization to complete and get the remoteId
243
243
  const { remoteId } = await aui.threadListItem().initialize();
244
244
 
245
245
  // Now safe to save the message using the remoteId
246
- await saveMessageToDatabase(remoteId, message);
246
+ await saveMessageToDatabase(remoteId, parentId, message);
247
247
  },
248
248
  // ...
249
249
  }),
@@ -424,6 +424,44 @@ const onEdit = async (message: AppendMessage) => {
424
424
  };
425
425
  ```
426
426
 
427
+ ### Branching Support
428
+
429
+ The `messages` array path assumes a linear conversation — each message's parent is the previous message. To support branching (e.g. regenerating responses creates alternative branches), use `ExportedMessageRepository.fromBranchableArray()` combined with `thread.import()`.
430
+
431
+ Each message must have an explicit `id` and `parentId`. Messages with the same `parentId` create branches:
432
+
433
+ ```tsx
434
+ import {
435
+ ExportedMessageRepository,
436
+ useExternalStoreRuntime,
437
+ } from "@assistant-ui/react";
438
+
439
+ // Your messages from the backend, each with an id and parentId
440
+ const backendMessages = [
441
+ { id: "user-1", role: "user", content: "Hello", parentId: null },
442
+ { id: "asst-1", role: "assistant", content: "Hi!", parentId: "user-1" },
443
+ // A second response to the same user message = a branch
444
+ { id: "asst-2", role: "assistant", content: "Hey!", parentId: "user-1" },
445
+ ];
446
+
447
+ // Convert to ExportedMessageRepository
448
+ const repo = ExportedMessageRepository.fromBranchableArray(
449
+ backendMessages.map((m) => ({
450
+ message: { id: m.id, role: m.role, content: m.content },
451
+ parentId: m.parentId,
452
+ })),
453
+ { headId: "asst-1" }, // which branch to display initially
454
+ );
455
+
456
+ // Import into the runtime
457
+ runtime.thread.import(repo);
458
+ ```
459
+
460
+ <Callout type="warn">
461
+ Messages in the array must be ordered so that parents appear before their
462
+ children. Each message **must** have an `id` field set.
463
+ </Callout>
464
+
427
465
  ### Tool Calling
428
466
 
429
467
  Support tool calls with proper result handling:
@@ -501,26 +501,69 @@ export function MyRuntimeProvider({ children }) {
501
501
  const { remoteId } = aui.threadListItem().getState();
502
502
  if (!remoteId) return { messages: [] };
503
503
 
504
- const messages = await db.messages.findByThreadId(remoteId);
504
+ const rows = await db.messages.findByThreadId(remoteId);
505
505
  return {
506
- messages: messages.map((m) => ({
507
- role: m.role,
508
- content: m.content,
509
- id: m.id,
510
- createdAt: new Date(m.createdAt),
511
- })),
506
+ messages: rows.map((row) => {
507
+ const common = {
508
+ id: row.id,
509
+ createdAt: new Date(row.createdAt),
510
+ };
511
+ // `content` is stored as JSON — parse back into message parts
512
+ const content = JSON.parse(row.content);
513
+
514
+ if (row.role === "user") {
515
+ return {
516
+ parentId: row.parentId,
517
+ message: {
518
+ ...common,
519
+ role: "user" as const,
520
+ content,
521
+ attachments: [],
522
+ metadata: { custom: {} },
523
+ },
524
+ };
525
+ }
526
+ if (row.role === "assistant") {
527
+ return {
528
+ parentId: row.parentId,
529
+ message: {
530
+ ...common,
531
+ role: "assistant" as const,
532
+ content,
533
+ status: { type: "complete", reason: "stop" } as const,
534
+ metadata: {
535
+ custom: {},
536
+ unstable_state: null,
537
+ unstable_annotations: [],
538
+ unstable_data: [],
539
+ steps: [],
540
+ },
541
+ },
542
+ };
543
+ }
544
+ return {
545
+ parentId: row.parentId,
546
+ message: {
547
+ ...common,
548
+ role: "system" as const,
549
+ content,
550
+ metadata: { custom: {} },
551
+ },
552
+ };
553
+ }),
512
554
  };
513
555
  },
514
556
 
515
- async append(message) {
557
+ async append({ message, parentId }) {
516
558
  // Wait for initialization to get remoteId (safe to call multiple times)
517
559
  const { remoteId } = await aui.threadListItem().initialize();
518
560
 
519
561
  await db.messages.create({
520
562
  threadId: remoteId,
521
- role: message.role,
522
- content: message.content,
563
+ parentId,
523
564
  id: message.id,
565
+ role: message.role,
566
+ content: JSON.stringify(message.content),
524
567
  createdAt: message.createdAt,
525
568
  });
526
569
  },
@@ -584,10 +627,10 @@ const aui = useAui();
584
627
 
585
628
  const history = useMemo<ThreadHistoryAdapter>(
586
629
  () => ({
587
- async append(message) {
630
+ async append({ message, parentId }) {
588
631
  // Wait for initialization - safe to call multiple times
589
632
  const { remoteId } = await aui.threadListItem().initialize();
590
- await db.messages.create({ threadId: remoteId, ...message });
633
+ await db.messages.create({ threadId: remoteId, parentId, ...message });
591
634
  },
592
635
  // ...
593
636
  }),
@@ -613,8 +656,9 @@ interface ThreadRecord {
613
656
  interface MessageRecord {
614
657
  id: string;
615
658
  threadId: string;
659
+ parentId: string | null;
616
660
  role: "user" | "assistant" | "system";
617
- content: any; // Store as JSON
661
+ content: string; // JSON-encoded message content parts
618
662
  createdAt: Date;
619
663
  }
620
664
  ```
@@ -693,18 +737,21 @@ Persist and resume conversations:
693
737
  ```tsx
694
738
  const historyAdapter: ThreadHistoryAdapter = {
695
739
  async load() {
696
- // Load messages from your storage
740
+ // Load messages from your storage.
741
+ // The API must return `{ messages: { parentId, message }[] }`
742
+ // where each `message` is a full ThreadMessage
743
+ // (including `metadata.custom`, plus `attachments` on user messages
744
+ // and `status` + the rest of `metadata` on assistant messages).
697
745
  const response = await fetch(`/api/thread/current`);
698
- const { messages } = await response.json();
699
- return { messages };
746
+ return await response.json();
700
747
  },
701
748
 
702
- async append(message) {
749
+ async append({ message, parentId }) {
703
750
  // Save new message to storage
704
751
  await fetch(`/api/thread/messages`, {
705
752
  method: "POST",
706
753
  headers: { "Content-Type": "application/json" },
707
- body: JSON.stringify({ message }),
754
+ body: JSON.stringify({ message, parentId }),
708
755
  });
709
756
  },
710
757
 
@@ -317,6 +317,64 @@ function AuthUI() {
317
317
 
318
318
  `AdkAuthCredential` supports all ADK auth types: `apiKey`, `http`, `oauth2`, `openIdConnect`, `serviceAccount`.
319
319
 
320
+ ### Input Requests
321
+
322
+ When an ADK Python 2.0+ Workflow's `RequestInput` node pauses execution to ask the user a question, ADK emits an `adk_request_input` function call marked as long-running. Respond with `useAdkSubmitInput` inside a tool UI — the helper wraps the answer as `{ result }` to match ADK's `unwrap_response` contract, so the Workflow node resumes with the unwrapped value:
323
+
324
+ ```tsx
325
+ import { makeAssistantToolUI } from "@assistant-ui/react";
326
+ import { useAdkSubmitInput } from "@assistant-ui/react-google-adk";
327
+
328
+ type RequestInputArgs = {
329
+ interrupt_id?: string;
330
+ message?: string;
331
+ payload?: unknown;
332
+ response_schema?: unknown;
333
+ };
334
+
335
+ export const RequestInputToolUI = makeAssistantToolUI<RequestInputArgs, unknown>({
336
+ toolName: "adk_request_input",
337
+ render: function RequestInputUI({ toolCallId, args, result }) {
338
+ const submitInput = useAdkSubmitInput();
339
+
340
+ if (result !== undefined) {
341
+ return <p>Answered: {String(result)}</p>;
342
+ }
343
+
344
+ return (
345
+ <form
346
+ onSubmit={(e) => {
347
+ e.preventDefault();
348
+ const value = (
349
+ e.currentTarget.elements.namedItem("answer") as HTMLInputElement
350
+ ).value;
351
+ submitInput(toolCallId, value);
352
+ }}
353
+ >
354
+ <p>{args.message ?? "Please provide input:"}</p>
355
+ <input name="answer" autoFocus />
356
+ <button type="submit">Submit</button>
357
+ </form>
358
+ );
359
+ },
360
+ });
361
+ ```
362
+
363
+ Register the tool UI inside `AssistantRuntimeProvider`:
364
+
365
+ ```tsx
366
+ <AssistantRuntimeProvider runtime={runtime}>
367
+ <RequestInputToolUI />
368
+ <Thread />
369
+ </AssistantRuntimeProvider>
370
+ ```
371
+
372
+ <Callout type="info">
373
+ `adk_request_input` is emitted only by ADK Python 2.0+ Workflow `RequestInput` nodes — ADK JS has no equivalent. Always respond via a tool UI with `useAdkSubmitInput`; HITL interrupts are automatically exempt from `autoCancelPendingToolCalls`, so typing a normal message in the composer will not overwrite the pending interrupt.
374
+ </Callout>
375
+
376
+ `useAdkSubmitInput` is sugar over the generic `addResult` — if you prefer, you can call `addResult({ result: value })` from inside the render function directly. The `{ result }` wrapper is required either way: the adapter JSON-stringifies the value before sending, and ADK's `unwrap_response` unwraps it on the backend before the Workflow node resumes.
377
+
320
378
  ### Artifacts
321
379
 
322
380
  Track file artifacts created or modified by the agent:
@@ -384,6 +442,8 @@ function PendingToolsIndicator() {
384
442
  }
385
443
  ```
386
444
 
445
+ This hook reports every tool call ADK marked via `long_running_tool_ids`, including HITL interrupts. To respond to a specific HITL type, see [Tool Confirmations](#tool-confirmations), [Auth Requests](#auth-requests), or [Input Requests](#input-requests).
446
+
387
447
  ### Per-Message Metadata
388
448
 
389
449
  Access grounding, citation, and token usage metadata per message:
@@ -581,6 +641,7 @@ The resolved `checkpointId` is passed to your `stream` callback via `config.chec
581
641
  | `useAdkSend()` | Send raw ADK messages |
582
642
  | `useAdkConfirmTool()` | Confirm or deny a pending tool confirmation |
583
643
  | `useAdkSubmitAuth()` | Submit auth credentials for a pending auth request |
644
+ | `useAdkSubmitInput()` | Submit the user's answer for a pending `adk_request_input` HITL interrupt |
584
645
  | `useAdkToolConfirmations()` | Pending tool confirmation requests |
585
646
  | `useAdkAuthRequests()` | Pending auth credential requests |
586
647
  | `useAdkLongRunningToolIds()` | IDs of long-running tools awaiting input |
@@ -596,6 +657,7 @@ The resolved `checkpointId` is passed to your `stream` callback via `config.chec
596
657
  | Tool calls & results | Supported |
597
658
  | Tool confirmations (`useAdkConfirmTool`) | Supported |
598
659
  | Auth credential flow (`useAdkSubmitAuth`) | Supported |
660
+ | Workflow input requests (`useAdkSubmitInput`, ADK Python 2.0+) | Supported |
599
661
  | Multi-agent (author/branch tracking) | Supported |
600
662
  | Agent transfer events | Supported |
601
663
  | Escalation detection | Supported |