@assistant-ui/mcp-docs-server 0.1.25 → 0.1.26
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 +4 -4
- package/.docs/organized/code-examples/with-a2a.md +5 -5
- package/.docs/organized/code-examples/with-ag-ui.md +6 -6
- package/.docs/organized/code-examples/with-ai-sdk-v6.md +7 -7
- package/.docs/organized/code-examples/with-artifacts.md +7 -7
- package/.docs/organized/code-examples/with-assistant-transport.md +5 -5
- package/.docs/organized/code-examples/with-chain-of-thought.md +7 -7
- package/.docs/organized/code-examples/with-cloud-standalone.md +8 -8
- package/.docs/organized/code-examples/with-cloud.md +7 -7
- package/.docs/organized/code-examples/with-custom-thread-list.md +7 -7
- package/.docs/organized/code-examples/with-elevenlabs-scribe.md +10 -10
- package/.docs/organized/code-examples/with-expo.md +14 -14
- package/.docs/organized/code-examples/with-external-store.md +5 -5
- package/.docs/organized/code-examples/with-ffmpeg.md +7 -7
- package/.docs/organized/code-examples/with-google-adk.md +5 -5
- package/.docs/organized/code-examples/with-heat-graph.md +4 -4
- package/.docs/organized/code-examples/with-interactables.md +778 -0
- package/.docs/organized/code-examples/with-langgraph.md +6 -6
- package/.docs/organized/code-examples/with-parent-id-grouping.md +6 -6
- package/.docs/organized/code-examples/with-react-hook-form.md +8 -8
- package/.docs/organized/code-examples/with-react-ink.md +2 -2
- package/.docs/organized/code-examples/with-react-router.md +10 -10
- package/.docs/organized/code-examples/with-store.md +5 -5
- package/.docs/organized/code-examples/with-tanstack.md +8 -8
- package/.docs/organized/code-examples/with-tap-runtime.md +8 -8
- package/.docs/raw/blog/2026-03-launch-week/index.mdx +31 -0
- package/.docs/raw/docs/(docs)/cli.mdx +60 -0
- package/.docs/raw/docs/(docs)/guides/attachments.mdx +65 -4
- package/.docs/raw/docs/(docs)/guides/interactables.mdx +292 -0
- package/.docs/raw/docs/(docs)/guides/message-timing.mdx +3 -3
- package/.docs/raw/docs/(docs)/guides/multi-agent.mdx +1 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/composer.mdx +128 -0
- package/.docs/raw/docs/cloud/ai-sdk-assistant-ui.mdx +6 -0
- package/.docs/raw/docs/cloud/ai-sdk.mdx +81 -1
- package/.docs/raw/docs/ink/primitives.mdx +141 -0
- package/.docs/raw/docs/primitives/action-bar.mdx +351 -0
- package/.docs/raw/docs/primitives/assistant-modal.mdx +215 -0
- package/.docs/raw/docs/primitives/attachment.mdx +216 -0
- package/.docs/raw/docs/primitives/branch-picker.mdx +221 -0
- package/.docs/raw/docs/primitives/chain-of-thought.mdx +311 -0
- package/.docs/raw/docs/primitives/composer.mdx +526 -0
- package/.docs/raw/docs/primitives/error.mdx +141 -0
- package/.docs/raw/docs/primitives/index.mdx +98 -0
- package/.docs/raw/docs/primitives/message.mdx +524 -0
- package/.docs/raw/docs/primitives/selection-toolbar.mdx +165 -0
- package/.docs/raw/docs/primitives/suggestion.mdx +242 -0
- package/.docs/raw/docs/primitives/thread-list.mdx +404 -0
- package/.docs/raw/docs/primitives/thread.mdx +482 -0
- package/.docs/raw/docs/ui/mention.mdx +168 -0
- package/package.json +3 -3
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Interactables
|
|
3
|
+
description: Persistent UI whose state can be read and updated by the AI assistant.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
import { InteractableSample } from "@/components/docs/samples/interactable";
|
|
7
|
+
|
|
8
|
+
Interactables are React components that live outside the chat message flow and have state that both the user and the AI can read and write. This enables AI-driven UI patterns where the assistant controls parts of your application beyond the chat window.
|
|
9
|
+
|
|
10
|
+
<InteractableSample />
|
|
11
|
+
|
|
12
|
+
## Overview
|
|
13
|
+
|
|
14
|
+
Unlike regular tool UIs that appear inline within messages, interactables:
|
|
15
|
+
|
|
16
|
+
- **Persist across messages** — they live outside the chat thread
|
|
17
|
+
- **Have shared state** — both the user (via React) and the AI (via auto-generated tools) can update them
|
|
18
|
+
- **Support partial updates** — the AI only needs to send the fields it wants to change
|
|
19
|
+
- **Are developer-placed** — you decide where they render in your app
|
|
20
|
+
- **Auto-register tools** — the AI automatically gets a tool to update each interactable's state
|
|
21
|
+
|
|
22
|
+
Common use cases:
|
|
23
|
+
|
|
24
|
+
- Task boards that the AI can add items to
|
|
25
|
+
- Data dashboards that update based on conversation
|
|
26
|
+
- Forms that the AI pre-fills
|
|
27
|
+
- Canvas/editor components that the AI can manipulate
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
### 1. Register the Interactables scope
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
import { useAui, Interactables, AssistantRuntimeProvider } from "@assistant-ui/react";
|
|
35
|
+
import { useChatRuntime } from "@assistant-ui/react-ai-sdk";
|
|
36
|
+
|
|
37
|
+
function MyRuntimeProvider({ children }: { children: React.ReactNode }) {
|
|
38
|
+
const runtime = useChatRuntime();
|
|
39
|
+
|
|
40
|
+
const aui = useAui({
|
|
41
|
+
interactables: Interactables(),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<AssistantRuntimeProvider aui={aui} runtime={runtime}>
|
|
46
|
+
{children}
|
|
47
|
+
</AssistantRuntimeProvider>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### 2. Create an interactable
|
|
53
|
+
|
|
54
|
+
<Callout type="warn">
|
|
55
|
+
Define the `stateSchema` and `initialState` **outside** the component (or
|
|
56
|
+
memoize them). Creating a new schema on every render will cause the
|
|
57
|
+
interactable to re-register and reset its state.
|
|
58
|
+
</Callout>
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
import { useInteractable } from "@assistant-ui/react";
|
|
62
|
+
import { z } from "zod";
|
|
63
|
+
|
|
64
|
+
const taskBoardSchema = z.object({
|
|
65
|
+
tasks: z.array(
|
|
66
|
+
z.object({
|
|
67
|
+
id: z.string(),
|
|
68
|
+
title: z.string(),
|
|
69
|
+
done: z.boolean(),
|
|
70
|
+
}),
|
|
71
|
+
),
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const taskBoardInitialState = { tasks: [] };
|
|
75
|
+
|
|
76
|
+
function TaskBoard() {
|
|
77
|
+
const [state, setState] = useInteractable("taskBoard", {
|
|
78
|
+
description: "A task board showing the user's tasks",
|
|
79
|
+
stateSchema: taskBoardSchema,
|
|
80
|
+
initialState: taskBoardInitialState,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<div>
|
|
85
|
+
<h2>Tasks</h2>
|
|
86
|
+
<ul>
|
|
87
|
+
{state.tasks.map((task) => (
|
|
88
|
+
<li key={task.id}>
|
|
89
|
+
<label>
|
|
90
|
+
<input
|
|
91
|
+
type="checkbox"
|
|
92
|
+
checked={task.done}
|
|
93
|
+
onChange={() =>
|
|
94
|
+
setState((prev) => ({
|
|
95
|
+
tasks: prev.tasks.map((t) =>
|
|
96
|
+
t.id === task.id ? { ...t, done: !t.done } : t,
|
|
97
|
+
),
|
|
98
|
+
}))
|
|
99
|
+
}
|
|
100
|
+
/>
|
|
101
|
+
{task.title}
|
|
102
|
+
</label>
|
|
103
|
+
</li>
|
|
104
|
+
))}
|
|
105
|
+
</ul>
|
|
106
|
+
</div>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### 3. Place it in your layout
|
|
112
|
+
|
|
113
|
+
```tsx
|
|
114
|
+
function App() {
|
|
115
|
+
return (
|
|
116
|
+
<MyRuntimeProvider>
|
|
117
|
+
<div className="flex">
|
|
118
|
+
<Thread className="flex-1" />
|
|
119
|
+
<TaskBoard /> {/* Lives outside the chat */}
|
|
120
|
+
</div>
|
|
121
|
+
</MyRuntimeProvider>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Now when the user says _"Add a task called 'Buy groceries'"_, the AI will automatically call the `update_taskBoard` tool to update the state. Thanks to partial updates, the AI only needs to send the fields it wants to change.
|
|
127
|
+
|
|
128
|
+
## Partial Updates
|
|
129
|
+
|
|
130
|
+
Auto-generated tools use a partial schema — all fields become optional. The AI only sends the fields it wants to change; omitted fields keep their current values.
|
|
131
|
+
|
|
132
|
+
```tsx
|
|
133
|
+
// If the state is { title: "My Note", content: "Hello", color: "yellow" }
|
|
134
|
+
// The AI can call: update_note({ color: "blue" })
|
|
135
|
+
// Result: { title: "My Note", content: "Hello", color: "blue" }
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
This is especially useful for large state objects where regenerating the entire state would be expensive and error-prone.
|
|
139
|
+
|
|
140
|
+
<Callout type="info">
|
|
141
|
+
Merge is shallow (one level deep). If the AI sends a nested object, it replaces
|
|
142
|
+
that entire field rather than deep-merging into it.
|
|
143
|
+
</Callout>
|
|
144
|
+
|
|
145
|
+
## Multiple Instances
|
|
146
|
+
|
|
147
|
+
You can render multiple interactables with the same `name` but different `id`s. Each gets its own update tool:
|
|
148
|
+
|
|
149
|
+
```tsx
|
|
150
|
+
import { useInteractable } from "@assistant-ui/react";
|
|
151
|
+
import { z } from "zod";
|
|
152
|
+
|
|
153
|
+
const noteSchema = z.object({
|
|
154
|
+
title: z.string(),
|
|
155
|
+
content: z.string(),
|
|
156
|
+
color: z.enum(["yellow", "blue", "green", "pink"]),
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
const noteInitialState = { title: "New Note", content: "", color: "yellow" as const };
|
|
160
|
+
|
|
161
|
+
function NoteCard({ noteId }: { noteId: string }) {
|
|
162
|
+
const [state] = useInteractable("note", {
|
|
163
|
+
id: noteId,
|
|
164
|
+
description: "A sticky note",
|
|
165
|
+
stateSchema: noteSchema,
|
|
166
|
+
initialState: noteInitialState,
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
return <div>{state.title}</div>;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function App() {
|
|
173
|
+
return (
|
|
174
|
+
<>
|
|
175
|
+
<NoteCard noteId="note-1" /> {/* → update_note_note-1 tool */}
|
|
176
|
+
<NoteCard noteId="note-2" /> {/* → update_note_note-2 tool */}
|
|
177
|
+
</>
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
When only one instance of a name exists, the tool is named `update_{name}` (e.g., `update_note`). When multiple instances exist, tools are named `update_{name}_{id}` (e.g., `update_note_note-1`).
|
|
183
|
+
|
|
184
|
+
## Selection
|
|
185
|
+
|
|
186
|
+
When multiple interactables are present, you can mark one as "selected" to tell the AI which one the user is focused on:
|
|
187
|
+
|
|
188
|
+
```tsx
|
|
189
|
+
function NoteCard({ noteId }: { noteId: string }) {
|
|
190
|
+
const [state, setState, { setSelected }] = useInteractable("note", {
|
|
191
|
+
id: noteId,
|
|
192
|
+
description: "A sticky note",
|
|
193
|
+
stateSchema: noteSchema,
|
|
194
|
+
initialState: noteInitialState,
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
return (
|
|
198
|
+
<div onClick={() => setSelected(true)}>
|
|
199
|
+
{state.title}
|
|
200
|
+
</div>
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
The AI sees `(SELECTED)` in the system prompt for the focused interactable, allowing it to prioritize that one in responses. For example, the user can say _"Change the color to blue"_ and the AI knows which note to update.
|
|
206
|
+
|
|
207
|
+
## API Reference
|
|
208
|
+
|
|
209
|
+
### `useInteractable`
|
|
210
|
+
|
|
211
|
+
Hook that registers an interactable and returns its state with a setter.
|
|
212
|
+
|
|
213
|
+
```tsx
|
|
214
|
+
const [state, setState, meta] = useInteractable<TState>(name, config);
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
**Parameters:**
|
|
218
|
+
|
|
219
|
+
| Parameter | Type | Description |
|
|
220
|
+
| --- | --- | --- |
|
|
221
|
+
| `name` | `string` | Name for the interactable (used in tool names) |
|
|
222
|
+
| `config.description` | `string` | Description shown to the AI |
|
|
223
|
+
| `config.stateSchema` | `StandardSchemaV1 \| JSONSchema7` | Schema for the state (e.g., a Zod schema) |
|
|
224
|
+
| `config.initialState` | `TState` | Initial state value |
|
|
225
|
+
| `config.id` | `string?` | Optional unique instance ID (auto-generated if omitted) |
|
|
226
|
+
| `config.selected` | `boolean?` | Whether this interactable is selected |
|
|
227
|
+
|
|
228
|
+
**Returns:** `[state, setState, { id, setSelected }]`
|
|
229
|
+
|
|
230
|
+
| Return | Type | Description |
|
|
231
|
+
| --- | --- | --- |
|
|
232
|
+
| `state` | `TState` | Current state |
|
|
233
|
+
| `setState` | `(updater: TState \| (prev: TState) => TState) => void` | State setter (like `useState`) |
|
|
234
|
+
| `meta.id` | `string` | The instance ID (auto-generated or provided) |
|
|
235
|
+
| `meta.setSelected` | `(selected: boolean) => void` | Mark this interactable as selected |
|
|
236
|
+
|
|
237
|
+
### `makeInteractable`
|
|
238
|
+
|
|
239
|
+
Declarative API that creates a component which registers an interactable when mounted. Useful for static configurations.
|
|
240
|
+
|
|
241
|
+
```tsx
|
|
242
|
+
import { makeInteractable } from "@assistant-ui/react";
|
|
243
|
+
|
|
244
|
+
const TaskBoardInteractable = makeInteractable({
|
|
245
|
+
name: "taskBoard",
|
|
246
|
+
description: "A task board showing the user's tasks",
|
|
247
|
+
stateSchema: taskBoardSchema,
|
|
248
|
+
initialState: taskBoardInitialState,
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// Mount it anywhere — renders nothing, just registers the interactable
|
|
252
|
+
function App() {
|
|
253
|
+
return (
|
|
254
|
+
<>
|
|
255
|
+
<TaskBoardInteractable />
|
|
256
|
+
<Thread />
|
|
257
|
+
</>
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### `Interactables`
|
|
263
|
+
|
|
264
|
+
The scope resource that manages all interactables. Register it via `useAui`:
|
|
265
|
+
|
|
266
|
+
```tsx
|
|
267
|
+
const aui = useAui({
|
|
268
|
+
interactables: Interactables(),
|
|
269
|
+
});
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## How It Works
|
|
273
|
+
|
|
274
|
+
When you call `useInteractable("taskBoard", config)`:
|
|
275
|
+
|
|
276
|
+
1. **Registration** — the interactable is registered in the `interactables` scope with its name, description, schema, and initial state.
|
|
277
|
+
2. **Tool generation** — an `update_taskBoard` frontend tool is automatically created with a partial schema (all fields optional). For multiple instances, tools are named `update_{name}_{id}`.
|
|
278
|
+
3. **System prompt** — the AI receives a system message describing the interactable, its current state, and whether it is selected.
|
|
279
|
+
4. **Streaming updates** — as the AI generates the tool arguments, the interactable's state updates progressively rather than waiting for complete arguments. This gives users immediate visual feedback.
|
|
280
|
+
5. **Partial merge** — only the fields the AI sends are updated; the rest are preserved.
|
|
281
|
+
6. **Bidirectional updates** — when the AI calls the tool, the state updates and React re-renders. When the user updates state via `setState`, the model context is notified so the AI sees the latest state on the next turn.
|
|
282
|
+
|
|
283
|
+
## Combining with Tools
|
|
284
|
+
|
|
285
|
+
You can use `Interactables` alongside `Tools`:
|
|
286
|
+
|
|
287
|
+
```tsx
|
|
288
|
+
const aui = useAui({
|
|
289
|
+
tools: Tools({ toolkit: myToolkit }),
|
|
290
|
+
interactables: Interactables(),
|
|
291
|
+
});
|
|
292
|
+
```
|
|
@@ -58,8 +58,8 @@ const AssistantMessage: FC = () => {
|
|
|
58
58
|
| `streamStartTime` | `number` | Unix timestamp when stream started |
|
|
59
59
|
| `firstTokenTime` | `number?` | Time to first text token (ms) |
|
|
60
60
|
| `totalStreamTime` | `number?` | Total stream duration (ms) |
|
|
61
|
-
| `tokenCount` | `number?` |
|
|
62
|
-
| `tokensPerSecond` | `number?` | Throughput (tokens/sec)
|
|
61
|
+
| `tokenCount` | `number?` | Output token count from message metadata usage |
|
|
62
|
+
| `tokensPerSecond` | `number?` | Throughput (tokens/sec), when token usage is available |
|
|
63
63
|
| `totalChunks` | `number` | Total stream chunks received |
|
|
64
64
|
| `toolCallCount` | `number` | Number of tool calls |
|
|
65
65
|
|
|
@@ -90,7 +90,7 @@ const runtime = useDataStreamRuntime({ api: "/api/chat" });
|
|
|
90
90
|
Timing is tracked automatically on the client side by observing streaming state transitions and content changes. Timing is finalized when each stream completes.
|
|
91
91
|
|
|
92
92
|
<Callout type="warn">
|
|
93
|
-
`tokenCount` and `tokensPerSecond`
|
|
93
|
+
`tokenCount` and `tokensPerSecond` require usage metadata from `finish` or `finish-step` in your AI SDK route. If usage metadata is not emitted, duration and TTFT metrics still work, but token-based metrics are omitted.
|
|
94
94
|
</Callout>
|
|
95
95
|
|
|
96
96
|
```tsx
|
|
@@ -171,3 +171,4 @@ function SubConversation({
|
|
|
171
171
|
|
|
172
172
|
- [Generative UI](/docs/guides/tool-ui) — Creating tool call UIs
|
|
173
173
|
- [MessagePartPrimitive](/docs/api-reference/primitives/message-part) — API reference for message part primitives
|
|
174
|
+
- [Sub-Agent Model Tracking](/docs/cloud/ai-sdk#sub-agent-model-tracking) — Track delegated model usage and costs in the Cloud dashboard
|
|
@@ -415,3 +415,131 @@ import { AuiIf } from "@assistant-ui/react";
|
|
|
415
415
|
{/* rendered if dictation is active */}
|
|
416
416
|
</AuiIf>
|
|
417
417
|
```
|
|
418
|
+
|
|
419
|
+
## Mention Primitives (Unstable)
|
|
420
|
+
|
|
421
|
+
<Callout type="warn">
|
|
422
|
+
These primitives are under the `Unstable_` prefix and may change without notice.
|
|
423
|
+
</Callout>
|
|
424
|
+
|
|
425
|
+
Primitives for an @-mention picker in the composer. See the [Mention component guide](/docs/ui/mention) for a pre-built implementation.
|
|
426
|
+
|
|
427
|
+
### Anatomy
|
|
428
|
+
|
|
429
|
+
```tsx
|
|
430
|
+
import { ComposerPrimitive } from "@assistant-ui/react";
|
|
431
|
+
|
|
432
|
+
const Composer = () => (
|
|
433
|
+
<ComposerPrimitive.Unstable_MentionRoot adapter={mentionAdapter}>
|
|
434
|
+
<ComposerPrimitive.Root>
|
|
435
|
+
<ComposerPrimitive.Input />
|
|
436
|
+
<ComposerPrimitive.Unstable_MentionPopover>
|
|
437
|
+
<ComposerPrimitive.Unstable_MentionCategories>
|
|
438
|
+
{(categories) =>
|
|
439
|
+
categories.map((cat) => (
|
|
440
|
+
<ComposerPrimitive.Unstable_MentionCategoryItem
|
|
441
|
+
key={cat.id}
|
|
442
|
+
categoryId={cat.id}
|
|
443
|
+
>
|
|
444
|
+
{cat.label}
|
|
445
|
+
</ComposerPrimitive.Unstable_MentionCategoryItem>
|
|
446
|
+
))
|
|
447
|
+
}
|
|
448
|
+
</ComposerPrimitive.Unstable_MentionCategories>
|
|
449
|
+
<ComposerPrimitive.Unstable_MentionItems>
|
|
450
|
+
{(items) =>
|
|
451
|
+
items.map((item) => (
|
|
452
|
+
<ComposerPrimitive.Unstable_MentionItem
|
|
453
|
+
key={item.id}
|
|
454
|
+
item={item}
|
|
455
|
+
>
|
|
456
|
+
{item.label}
|
|
457
|
+
</ComposerPrimitive.Unstable_MentionItem>
|
|
458
|
+
))
|
|
459
|
+
}
|
|
460
|
+
</ComposerPrimitive.Unstable_MentionItems>
|
|
461
|
+
<ComposerPrimitive.Unstable_MentionBack>
|
|
462
|
+
Back
|
|
463
|
+
</ComposerPrimitive.Unstable_MentionBack>
|
|
464
|
+
</ComposerPrimitive.Unstable_MentionPopover>
|
|
465
|
+
</ComposerPrimitive.Root>
|
|
466
|
+
</ComposerPrimitive.Unstable_MentionRoot>
|
|
467
|
+
);
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
### Unstable_MentionRoot
|
|
471
|
+
|
|
472
|
+
Provider that wraps the composer with mention trigger detection, keyboard navigation, and popover state.
|
|
473
|
+
|
|
474
|
+
| Prop | Type | Default | Description |
|
|
475
|
+
| --- | --- | --- | --- |
|
|
476
|
+
| `adapter` | `Unstable_MentionAdapter` | — | Provides categories, items, and search |
|
|
477
|
+
| `trigger` | `string` | `"@"` | Character(s) that activate the popover |
|
|
478
|
+
| `formatter` | `Unstable_DirectiveFormatter` | Default | Serializer/parser for mention directives |
|
|
479
|
+
|
|
480
|
+
### Unstable_MentionPopover
|
|
481
|
+
|
|
482
|
+
Container that only renders when a mention trigger is active. Renders a `<div>` with `role="listbox"`.
|
|
483
|
+
|
|
484
|
+
### Unstable_MentionCategories
|
|
485
|
+
|
|
486
|
+
Renders the top-level category list. Accepts a render function `(categories) => ReactNode`. Hidden when a category is selected or when in search mode.
|
|
487
|
+
|
|
488
|
+
### Unstable_MentionCategoryItem
|
|
489
|
+
|
|
490
|
+
A button that drills into a category. Renders `role="option"` with automatic `data-highlighted` and `aria-selected` when keyboard-navigated.
|
|
491
|
+
|
|
492
|
+
| Prop | Type | Description |
|
|
493
|
+
| --- | --- | --- |
|
|
494
|
+
| `categoryId` | `string` | The category to select on click |
|
|
495
|
+
|
|
496
|
+
### Unstable_MentionItems
|
|
497
|
+
|
|
498
|
+
Renders the item list for the active category or search results. Accepts a render function `(items) => ReactNode`. Hidden when no category is selected and not in search mode.
|
|
499
|
+
|
|
500
|
+
### Unstable_MentionItem
|
|
501
|
+
|
|
502
|
+
A button that inserts a mention into the composer. Renders `role="option"` with automatic `data-highlighted` and `aria-selected` when keyboard-navigated.
|
|
503
|
+
|
|
504
|
+
| Prop | Type | Description |
|
|
505
|
+
| --- | --- | --- |
|
|
506
|
+
| `item` | `Unstable_MentionItem` | The item to insert on click |
|
|
507
|
+
|
|
508
|
+
### Unstable_MentionBack
|
|
509
|
+
|
|
510
|
+
A button that navigates back from items to the category list. Only renders when a category is active.
|
|
511
|
+
|
|
512
|
+
### unstable_useMentionContext
|
|
513
|
+
|
|
514
|
+
Hook to access the mention popover state and actions from within `Unstable_MentionRoot`.
|
|
515
|
+
|
|
516
|
+
```tsx
|
|
517
|
+
const {
|
|
518
|
+
open, // boolean — whether popover is visible
|
|
519
|
+
query, // string — text after the trigger character
|
|
520
|
+
isSearchMode, // boolean — whether showing search results
|
|
521
|
+
highlightedIndex,
|
|
522
|
+
categories,
|
|
523
|
+
items,
|
|
524
|
+
activeCategoryId,
|
|
525
|
+
selectCategory,
|
|
526
|
+
selectItem,
|
|
527
|
+
goBack,
|
|
528
|
+
close,
|
|
529
|
+
handleKeyDown,
|
|
530
|
+
formatter,
|
|
531
|
+
} = unstable_useMentionContext();
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
### unstable_useToolMentionAdapter
|
|
535
|
+
|
|
536
|
+
Hook that creates a `Unstable_MentionAdapter` from registered tools (via `useAssistantTool`).
|
|
537
|
+
|
|
538
|
+
```tsx
|
|
539
|
+
import { unstable_useToolMentionAdapter } from "@assistant-ui/react";
|
|
540
|
+
|
|
541
|
+
const adapter = unstable_useToolMentionAdapter({
|
|
542
|
+
formatLabel: (name) => name.replaceAll("_", " "),
|
|
543
|
+
categoryLabel: "Tools",
|
|
544
|
+
});
|
|
545
|
+
```
|
|
@@ -256,6 +256,12 @@ const cloud = new AssistantCloud({
|
|
|
256
256
|
|
|
257
257
|
Return `null` from `beforeReport` to skip reporting a specific run. To disable telemetry entirely, pass `telemetry: false`.
|
|
258
258
|
|
|
259
|
+
### Sub-Agent Model Tracking
|
|
260
|
+
|
|
261
|
+
When tool calls delegate to a different model (e.g., the main run uses GPT but a tool invokes Gemini), you can track the delegated model's usage. Pass sampling call data through `messageMetadata.samplingCalls` in your API route, and the telemetry reporter will automatically include it in the report.
|
|
262
|
+
|
|
263
|
+
See the [AI SDK Telemetry guide](/docs/cloud/ai-sdk#sub-agent-model-tracking) for the full setup with `createSamplingCollector` and `wrapSamplingHandler`.
|
|
264
|
+
|
|
259
265
|
## Authentication
|
|
260
266
|
|
|
261
267
|
The example above uses anonymous mode (browser session-based user ID) via the env var. For production apps with user accounts, pass an explicit cloud instance:
|
|
@@ -252,7 +252,7 @@ export async function POST(req: Request) {
|
|
|
252
252
|
```
|
|
253
253
|
|
|
254
254
|
<Callout type="info">
|
|
255
|
-
The standalone hook does not capture `duration_ms`, per-step breakdowns (`steps`),
|
|
255
|
+
The standalone hook captures message metadata when it is JSON-serializable, but it does not capture `duration_ms`, per-step breakdowns (`steps`), or `"error"` status. Those require the full runtime integration available via [`useChatRuntime`](/docs/cloud/ai-sdk-assistant-ui).
|
|
256
256
|
</Callout>
|
|
257
257
|
|
|
258
258
|
### Customizing Reports
|
|
@@ -274,6 +274,86 @@ const cloud = new AssistantCloud({
|
|
|
274
274
|
|
|
275
275
|
Return `null` from `beforeReport` to skip reporting a specific run. To disable telemetry entirely, pass `telemetry: false`.
|
|
276
276
|
|
|
277
|
+
### Sub-Agent Model Tracking
|
|
278
|
+
|
|
279
|
+
In multi-agent setups where tool calls delegate to a different model (e.g., the main run uses GPT but a tool invokes Gemini), you can track the delegated model's usage by passing sampling call data through `messageMetadata`.
|
|
280
|
+
|
|
281
|
+
**Step 1: Collect sampling data on the server**
|
|
282
|
+
|
|
283
|
+
Use `createSamplingCollector` and `wrapSamplingHandler` from `assistant-cloud` to capture LLM calls made during tool execution:
|
|
284
|
+
|
|
285
|
+
```ts title="app/api/chat/route.ts"
|
|
286
|
+
import { streamText } from "ai";
|
|
287
|
+
import { openai } from "@ai-sdk/openai";
|
|
288
|
+
import {
|
|
289
|
+
createSamplingCollector,
|
|
290
|
+
wrapSamplingHandler,
|
|
291
|
+
} from "assistant-cloud";
|
|
292
|
+
|
|
293
|
+
export async function POST(req: Request) {
|
|
294
|
+
const { messages } = await req.json();
|
|
295
|
+
|
|
296
|
+
// Collect sub-agent sampling calls per tool call
|
|
297
|
+
const samplingCalls: Record<string, SamplingCallData[]> = {};
|
|
298
|
+
|
|
299
|
+
const result = streamText({
|
|
300
|
+
model: openai("gpt-4o"),
|
|
301
|
+
messages,
|
|
302
|
+
tools: {
|
|
303
|
+
delegate_to_gemini: tool({
|
|
304
|
+
parameters: z.object({ task: z.string() }),
|
|
305
|
+
execute: async ({ task }, { toolCallId }) => {
|
|
306
|
+
const collector = createSamplingCollector();
|
|
307
|
+
// Your sub-agent logic that calls another model
|
|
308
|
+
const result = await runSubAgent(task, {
|
|
309
|
+
onSamplingCall: collector.collect,
|
|
310
|
+
});
|
|
311
|
+
samplingCalls[toolCallId] = collector.getCalls();
|
|
312
|
+
return result;
|
|
313
|
+
},
|
|
314
|
+
}),
|
|
315
|
+
},
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
return result.toUIMessageStreamResponse({
|
|
319
|
+
messageMetadata: ({ part }) => {
|
|
320
|
+
if (part.type === "finish") {
|
|
321
|
+
return {
|
|
322
|
+
usage: part.totalUsage,
|
|
323
|
+
samplingCalls, // attach collected sampling data
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
if (part.type === "finish-step") {
|
|
327
|
+
return { modelId: part.response.modelId };
|
|
328
|
+
}
|
|
329
|
+
return undefined;
|
|
330
|
+
},
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
**Step 2: That's it.** The telemetry reporter automatically reads `samplingCalls` from message metadata and attaches the data to matching tool calls in the report. The Cloud dashboard will show each delegated model in the model distribution chart with its own token and cost breakdown.
|
|
336
|
+
|
|
337
|
+
<Callout type="info">
|
|
338
|
+
For MCP tools that use the sampling protocol, `wrapSamplingHandler` can wrap the MCP client's sampling handler directly to capture all nested LLM calls transparently.
|
|
339
|
+
</Callout>
|
|
340
|
+
|
|
341
|
+
<Callout type="tip">
|
|
342
|
+
**On older versions** that don't yet read `samplingCalls` from metadata, use `beforeReport` to inject the data manually:
|
|
343
|
+
|
|
344
|
+
```ts
|
|
345
|
+
telemetry: {
|
|
346
|
+
beforeReport: (report) => ({
|
|
347
|
+
...report,
|
|
348
|
+
tool_calls: report.tool_calls?.map((tc) => ({
|
|
349
|
+
...tc,
|
|
350
|
+
sampling_calls: samplingCalls[tc.tool_call_id],
|
|
351
|
+
})),
|
|
352
|
+
}),
|
|
353
|
+
}
|
|
354
|
+
```
|
|
355
|
+
</Callout>
|
|
356
|
+
|
|
277
357
|
## Authentication
|
|
278
358
|
|
|
279
359
|
The example above uses anonymous mode (browser session-based user ID) via the env var. For production apps with user accounts, pass an explicit cloud instance:
|