@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.
- package/.docs/organized/code-examples/waterfall.md +6 -6
- package/.docs/organized/code-examples/with-a2a.md +7 -7
- package/.docs/organized/code-examples/with-ag-ui.md +8 -8
- package/.docs/organized/code-examples/with-ai-sdk-v6.md +9 -9
- package/.docs/organized/code-examples/with-artifacts.md +9 -9
- package/.docs/organized/code-examples/with-assistant-transport.md +7 -7
- package/.docs/organized/code-examples/with-chain-of-thought.md +9 -9
- package/.docs/organized/code-examples/with-cloud-standalone.md +9 -9
- package/.docs/organized/code-examples/with-cloud.md +9 -9
- package/.docs/organized/code-examples/with-custom-thread-list.md +9 -9
- package/.docs/organized/code-examples/with-elevenlabs-conversational.md +11 -11
- package/.docs/organized/code-examples/with-elevenlabs-scribe.md +11 -11
- package/.docs/organized/code-examples/with-expo.md +17 -17
- package/.docs/organized/code-examples/with-external-store.md +7 -7
- package/.docs/organized/code-examples/with-ffmpeg.md +9 -9
- package/.docs/organized/code-examples/with-generative-ui.md +841 -0
- package/.docs/organized/code-examples/with-google-adk.md +7 -7
- package/.docs/organized/code-examples/with-heat-graph.md +6 -6
- package/.docs/organized/code-examples/with-interactables.md +9 -9
- package/.docs/organized/code-examples/with-langgraph.md +9 -9
- package/.docs/organized/code-examples/with-livekit.md +50 -14
- package/.docs/organized/code-examples/with-opencode.md +2394 -0
- package/.docs/organized/code-examples/with-parent-id-grouping.md +8 -8
- package/.docs/organized/code-examples/with-react-hook-form.md +10 -10
- package/.docs/organized/code-examples/with-react-ink.md +3 -3
- package/.docs/organized/code-examples/with-react-router.md +8 -8
- package/.docs/organized/code-examples/with-store.md +6 -6
- package/.docs/organized/code-examples/with-tanstack.md +10 -10
- package/.docs/organized/code-examples/with-tap-runtime.md +7 -7
- package/.docs/raw/docs/(docs)/guides/mentions.mdx +406 -0
- package/.docs/raw/docs/(docs)/guides/slash-commands.mdx +275 -0
- package/.docs/raw/docs/(docs)/guides/voice.mdx +10 -3
- package/.docs/raw/docs/primitives/composer.mdx +27 -4
- package/.docs/raw/docs/runtimes/custom/custom-thread-list.mdx +2 -2
- package/.docs/raw/docs/runtimes/custom/external-store.mdx +38 -0
- package/.docs/raw/docs/runtimes/custom/local.mdx +65 -18
- package/.docs/raw/docs/runtimes/google-adk/index.mdx +62 -0
- package/.docs/raw/docs/runtimes/langgraph/index.mdx +137 -1
- 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
|
|
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
|
|
105
|
+
### Unstable Trigger Popovers
|
|
106
106
|
|
|
107
|
-
Composer
|
|
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
|
-
<
|
|
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
|
|
504
|
+
const rows = await db.messages.findByThreadId(remoteId);
|
|
505
505
|
return {
|
|
506
|
-
messages:
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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 |
|