@assistant-ui/mcp-docs-server 0.1.35 → 0.1.36
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 +1 -1
- package/.docs/organized/code-examples/with-a2a.md +2 -2
- package/.docs/organized/code-examples/with-ag-ui.md +2 -2
- package/.docs/organized/code-examples/with-ai-sdk-v6.md +4 -4
- package/.docs/organized/code-examples/with-artifacts.md +4 -4
- package/.docs/organized/code-examples/with-assistant-transport.md +70 -54
- package/.docs/organized/code-examples/with-browser-extension.md +4 -4
- package/.docs/organized/code-examples/with-chain-of-thought.md +40 -89
- package/.docs/organized/code-examples/with-cloud-standalone.md +4 -4
- package/.docs/organized/code-examples/with-cloud.md +4 -4
- package/.docs/organized/code-examples/with-custom-thread-list.md +4 -4
- package/.docs/organized/code-examples/with-elevenlabs-conversational.md +6 -6
- package/.docs/organized/code-examples/with-elevenlabs-scribe.md +6 -6
- package/.docs/organized/code-examples/with-eve.md +343 -0
- package/.docs/organized/code-examples/with-expo.md +909 -932
- package/.docs/organized/code-examples/with-external-store.md +2 -2
- package/.docs/organized/code-examples/with-ffmpeg.md +5 -8
- package/.docs/organized/code-examples/with-generative-ui.md +5 -5
- package/.docs/organized/code-examples/with-google-adk.md +2 -2
- package/.docs/organized/code-examples/with-heat-graph.md +1 -1
- package/.docs/organized/code-examples/with-image-generation.md +4 -4
- package/.docs/organized/code-examples/with-interactables.md +166 -338
- package/.docs/organized/code-examples/with-langchain.md +4 -4
- package/.docs/organized/code-examples/with-langgraph.md +20 -157
- package/.docs/organized/code-examples/with-livekit.md +6 -6
- package/.docs/organized/code-examples/with-mcp.md +4 -4
- package/.docs/organized/code-examples/with-opencode.md +3 -3
- package/.docs/organized/code-examples/with-pi.md +9 -7
- package/.docs/organized/code-examples/with-react-hook-form.md +13 -6
- package/.docs/organized/code-examples/with-react-ink-web.md +5 -5
- package/.docs/organized/code-examples/with-react-ink.md +1 -1
- package/.docs/organized/code-examples/with-react-router.md +10 -10
- package/.docs/organized/code-examples/with-resumable-stream.md +5 -5
- package/.docs/organized/code-examples/with-store.md +1 -1
- package/.docs/organized/code-examples/with-tanstack.md +5 -5
- package/.docs/organized/code-examples/with-tap-runtime.md +2 -2
- package/.docs/organized/code-examples/with-virtualized-thread.md +44 -24
- package/.docs/raw/docs/(docs)/cli.mdx +4 -2
- package/.docs/raw/docs/(docs)/devtools.mdx +25 -2
- package/.docs/raw/docs/(docs)/index.mdx +5 -2
- package/.docs/raw/docs/(docs)/installation.mdx +5 -2
- package/.docs/raw/docs/(reference)/api-reference/external-store/message-conversion.mdx +16 -16
- package/.docs/raw/docs/(reference)/api-reference/hooks/composer-triggers.mdx +73 -42
- package/.docs/raw/docs/(reference)/api-reference/hooks/model-context.mdx +3 -20
- package/.docs/raw/docs/(reference)/api-reference/hooks/primitives.mdx +234 -166
- package/.docs/raw/docs/(reference)/api-reference/integrations/eve.mdx +51 -0
- package/.docs/raw/docs/(reference)/api-reference/integrations/index.mdx +4 -1
- package/.docs/raw/docs/(reference)/api-reference/integrations/react-ai-sdk.mdx +70 -38
- package/.docs/raw/docs/(reference)/api-reference/primitives/action-bar.mdx +30 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/attachment.mdx +7 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/branch-picker.mdx +26 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/chain-of-thought.mdx +15 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/composer.mdx +103 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/message-part.mdx +16 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/message.mdx +56 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/queue-item.mdx +12 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/selection-toolbar.mdx +12 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/suggestion.mdx +14 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/thread.mdx +57 -1
- package/.docs/raw/docs/(reference)/api-reference/tools/index.mdx +6 -0
- package/.docs/raw/docs/(reference)/api-reference/tools/interactables-legacy.mdx +55 -0
- package/.docs/raw/docs/(reference)/api-reference/tools/interactables.mdx +151 -0
- package/.docs/raw/docs/(reference)/api-reference/tools/rendering.mdx +15 -15
- package/.docs/raw/docs/(reference)/api-reference/tools/toolkits.mdx +20 -20
- package/.docs/raw/docs/(reference)/api-reference/utilities/miscellaneous.mdx +25 -9
- package/.docs/raw/docs/guides/headless-composer-input.mdx +113 -0
- package/.docs/raw/docs/guides/latex.mdx +28 -22
- package/.docs/raw/docs/guides/mentions.mdx +32 -7
- package/.docs/raw/docs/guides/slash-commands.mdx +3 -6
- package/.docs/raw/docs/guides/speech.mdx +5 -7
- package/.docs/raw/docs/guides/virtualization.mdx +77 -7
- package/.docs/raw/docs/guides/voice.mdx +3 -2
- package/.docs/raw/docs/integrations/attachments/custom-adapter.mdx +4 -12
- package/.docs/raw/docs/integrations/auth/better-auth.mdx +7 -10
- package/.docs/raw/docs/integrations/auth/clerk.mdx +3 -9
- package/.docs/raw/docs/integrations/auth/next-auth.mdx +3 -8
- package/.docs/raw/docs/integrations/frameworks/ai-sdk.mdx +8 -5
- package/.docs/raw/docs/integrations/index.mdx +5 -12
- package/.docs/raw/docs/integrations/observability/helicone.mdx +3 -4
- package/.docs/raw/docs/integrations/observability/langfuse.mdx +3 -5
- package/.docs/raw/docs/integrations/observability/langsmith.mdx +3 -2
- package/.docs/raw/docs/integrations/persistence/custom-adapter.mdx +4 -3
- package/.docs/raw/docs/primitives/composer.mdx +8 -0
- package/.docs/raw/docs/primitives/thread.mdx +24 -0
- package/.docs/raw/docs/runtimes/ag-ui/runtime-options.mdx +21 -0
- package/.docs/raw/docs/runtimes/custom/external-store.mdx +54 -0
- package/.docs/raw/docs/runtimes/eve/overview.mdx +100 -0
- package/.docs/raw/docs/runtimes/eve/quickstart.mdx +143 -0
- package/.docs/raw/docs/runtimes/langchain.mdx +386 -18
- package/.docs/raw/docs/runtimes/langgraph/quickstart.mdx +1 -1
- package/.docs/raw/docs/runtimes/langgraph/tutorial/part-1.mdx +1 -1
- package/.docs/raw/docs/runtimes/pick-a-runtime.mdx +6 -0
- package/.docs/raw/docs/tools/defining-tools.mdx +4 -0
- package/.docs/raw/docs/tools/interactables-legacy.mdx +410 -0
- package/.docs/raw/docs/tools/interactables.mdx +892 -223
- package/.docs/raw/docs/tools/mcp.mdx +4 -4
- package/.docs/raw/docs/tools/multi-agent.mdx +5 -0
- package/.docs/raw/docs/ui/composer-trigger-popover.mdx +2 -0
- package/.docs/raw/docs/ui/file.mdx +1 -1
- package/.docs/raw/docs/ui/part-grouping.mdx +38 -0
- package/.docs/raw/docs/utilities/react-o11y.mdx +92 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -2
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/src/index.ts +14 -6
- package/src/tools/tests/mcp-protocol.test.ts +9 -0
|
@@ -8,12 +8,9 @@ Mentions let users type `@` in the composer to open a popover picker, select an
|
|
|
8
8
|
|
|
9
9
|
## How It Works
|
|
10
10
|
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
Directive inserted ← User selects item from popover
|
|
15
|
-
↓
|
|
16
|
-
Message sent with ":tool[Label]{name=id}" in text
|
|
11
|
+
```mermaid
|
|
12
|
+
flowchart LR
|
|
13
|
+
type["User types @"] --> trigger["Trigger detected"] --> adapter["Adapter provides<br/>categories / items"] --> select["User selects item<br/>from popover"] --> insert["Directive inserted"] --> sent["Message sent with<br/>:tool[Label]{name=id} text"]
|
|
17
14
|
```
|
|
18
15
|
|
|
19
16
|
The mention system has three layers:
|
|
@@ -85,7 +82,35 @@ const myAdapter: Unstable_TriggerAdapter = {
|
|
|
85
82
|
|
|
86
83
|
### Async Mention Search
|
|
87
84
|
|
|
88
|
-
The
|
|
85
|
+
The built-in `unstable_useLiveCompletionAdapter` wraps an async fetcher with debouncing, stale-request cancellation (results for an outdated query are dropped), and a single-entry cache. Its `search` returns the last results synchronously and schedules a debounced fetch when the query changes; when results arrive the returned `adapter` re-creates, which re-runs the popover lookup so the fresh items render. It also reports `isLoading`, which you pass to the popover to show a loading state.
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
import {
|
|
89
|
+
unstable_useLiveCompletionAdapter,
|
|
90
|
+
unstable_defaultDirectiveFormatter,
|
|
91
|
+
} from "@assistant-ui/react";
|
|
92
|
+
import { ComposerTriggerPopover } from "@/components/assistant-ui/composer-trigger-popover";
|
|
93
|
+
|
|
94
|
+
function MentionPopover() {
|
|
95
|
+
const mentions = unstable_useLiveCompletionAdapter({
|
|
96
|
+
fetcher: async (query) => {
|
|
97
|
+
const users = await fetchUsers(query);
|
|
98
|
+
return users.map((u) => ({ id: u.id, type: "user", label: u.name }));
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<ComposerTriggerPopover
|
|
104
|
+
char="@"
|
|
105
|
+
adapter={mentions.adapter}
|
|
106
|
+
isLoading={mentions.isLoading}
|
|
107
|
+
directive={{ formatter: unstable_defaultDirectiveFormatter }}
|
|
108
|
+
/>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
The adapter interface is itself synchronous, so you can also wire async data by hand when you need a custom cache, no debounce, or an existing query client. Load results into React state (or a query cache) and read the current snapshot inside the adapter methods. The adapter re-creates on each render, so the popover always sees the latest results.
|
|
89
114
|
|
|
90
115
|
**With React state and `useEffect`:**
|
|
91
116
|
|
|
@@ -8,12 +8,9 @@ Slash commands let users type `/` in the composer to open a popover, browse avai
|
|
|
8
8
|
|
|
9
9
|
## How It Works
|
|
10
10
|
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
Callback fired ← User selects command from popover
|
|
15
|
-
↓
|
|
16
|
-
Directive chip left in composer (or removed if removeOnExecute)
|
|
11
|
+
```mermaid
|
|
12
|
+
flowchart LR
|
|
13
|
+
type["User types /"] --> trigger["Trigger detected"] --> adapter["Adapter provides<br/>commands"] --> select["User selects command<br/>from popover"] --> callback["Callback fired"] --> chip["Directive chip left in composer<br/>(or removed if removeOnExecute)"]
|
|
17
14
|
```
|
|
18
15
|
|
|
19
16
|
The slash command system is built on the same [trigger popover architecture](#trigger-popover-architecture) as mentions. A slash command declares its behavior with a `<TriggerPopover.Action>` sub-primitive whose `onExecute` callback fires when an item is chosen.
|
|
@@ -57,24 +57,22 @@ const runtime = useChatRuntime({
|
|
|
57
57
|
The default action bar does not include a speech button. Add `ActionBarPrimitive.Speak` and `ActionBarPrimitive.StopSpeaking` to your assistant message action bar:
|
|
58
58
|
|
|
59
59
|
```tsx
|
|
60
|
-
import { ActionBarPrimitive,
|
|
60
|
+
import { ActionBarPrimitive, AuiIf } from "@assistant-ui/react";
|
|
61
61
|
import { AudioLinesIcon, StopCircleIcon } from "lucide-react";
|
|
62
62
|
|
|
63
63
|
const AssistantActionBar = () => {
|
|
64
|
-
const isSpeaking = useMessageTTS();
|
|
65
|
-
|
|
66
64
|
return (
|
|
67
65
|
<ActionBarPrimitive.Root>
|
|
68
|
-
{
|
|
66
|
+
<AuiIf condition={(s) => s.message.speech == null}>
|
|
69
67
|
<ActionBarPrimitive.Speak>
|
|
70
68
|
<AudioLinesIcon />
|
|
71
69
|
</ActionBarPrimitive.Speak>
|
|
72
|
-
|
|
73
|
-
{
|
|
70
|
+
</AuiIf>
|
|
71
|
+
<AuiIf condition={(s) => s.message.speech != null}>
|
|
74
72
|
<ActionBarPrimitive.StopSpeaking>
|
|
75
73
|
<StopCircleIcon />
|
|
76
74
|
</ActionBarPrimitive.StopSpeaking>
|
|
77
|
-
|
|
75
|
+
</AuiIf>
|
|
78
76
|
<ActionBarPrimitive.Copy />
|
|
79
77
|
</ActionBarPrimitive.Root>
|
|
80
78
|
);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Thread Virtualization
|
|
3
|
-
description: Render very long threads with @tanstack/react-virtual and ThreadPrimitive.MessageByIndex.
|
|
3
|
+
description: Render very long threads with @tanstack/react-virtual, with ThreadPrimitive.Unstable_MessageById and ThreadPrimitive.MessageByIndex.
|
|
4
4
|
platforms: ["react"]
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -10,9 +10,35 @@ Virtualization mounts only the messages near the viewport and represents the res
|
|
|
10
10
|
|
|
11
11
|
Probably not. The default kit already renders message bodies with `content-visibility: auto` and `contain-intrinsic-size`, which skips paint work for off-screen messages, and `ThreadPrimitive.Viewport` handles auto-scroll. That covers typical threads. Reach for virtualization when React mount and update cost itself becomes the bottleneck: threads with hundreds to thousands of messages, or very heavy per-message content, where typing latency degrades because every message stays mounted.
|
|
12
12
|
|
|
13
|
-
## Rendering
|
|
13
|
+
## Rendering Messages By Id
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
For virtualized rows, use message ids as the row identity. `unstable_useThreadMessageIds` returns the thread's ids with stable array identity across content-only updates, and `ThreadPrimitive.Unstable_MessageById` renders one message with the same `components` surface as `MessageByIndex`. Unknown ids render `null`, which is useful when a virtual row unmounts after a message was removed.
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
const MESSAGE_COMPONENTS = { UserMessage, AssistantMessage };
|
|
19
|
+
|
|
20
|
+
const messageIds = unstable_useThreadMessageIds();
|
|
21
|
+
|
|
22
|
+
return messageIds.map((messageId) => (
|
|
23
|
+
<ThreadPrimitive.Unstable_MessageById
|
|
24
|
+
key={messageId}
|
|
25
|
+
messageId={messageId}
|
|
26
|
+
components={MESSAGE_COMPONENTS}
|
|
27
|
+
/>
|
|
28
|
+
));
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
<Callout type="warn">
|
|
32
|
+
`unstable_useThreadMessageIds` and `ThreadPrimitive.Unstable_MessageById` are
|
|
33
|
+
experimental and may change in any release.
|
|
34
|
+
</Callout>
|
|
35
|
+
|
|
36
|
+
## Rendering Messages By Index
|
|
37
|
+
|
|
38
|
+
`ThreadPrimitive.MessageByIndex` is still supported and is the smaller API when
|
|
39
|
+
you already have a stable index from a fixed-order list. It renders a single
|
|
40
|
+
message at that index and memoizes on the index plus the per-field `components`
|
|
41
|
+
identity:
|
|
16
42
|
|
|
17
43
|
```tsx
|
|
18
44
|
const MESSAGE_COMPONENTS = { UserMessage, AssistantMessage };
|
|
@@ -20,15 +46,59 @@ const MESSAGE_COMPONENTS = { UserMessage, AssistantMessage };
|
|
|
20
46
|
<ThreadPrimitive.MessageByIndex index={index} components={MESSAGE_COMPONENTS} />;
|
|
21
47
|
```
|
|
22
48
|
|
|
49
|
+
For virtualizers, prefer the id-based API above when you can. Index rows are more
|
|
50
|
+
fragile when messages are inserted, removed, reordered, or when a virtual row
|
|
51
|
+
briefly outlives the item it was created for.
|
|
52
|
+
|
|
23
53
|
## Grouping into turns
|
|
24
54
|
|
|
25
|
-
|
|
55
|
+
The example virtualizes per user turn (a user message plus the responses that follow it), which gives the virtualizer stable, meaningfully sized items. Keep the id and role together in a stable row shape so the turn array only rebuilds when message membership or roles change:
|
|
26
56
|
|
|
27
57
|
```tsx
|
|
28
|
-
|
|
29
|
-
|
|
58
|
+
type MessageRow = {
|
|
59
|
+
id: string;
|
|
60
|
+
role: "user" | "assistant" | "system";
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const useThreadMessageRows = (): readonly MessageRow[] => {
|
|
64
|
+
const prevRowsRef = useRef<readonly MessageRow[]>([]);
|
|
65
|
+
|
|
66
|
+
return useAuiState((s) => {
|
|
67
|
+
const messages = s.thread.messages;
|
|
68
|
+
const prev = prevRowsRef.current;
|
|
69
|
+
if (
|
|
70
|
+
prev.length === messages.length &&
|
|
71
|
+
prev.every((row, index) => {
|
|
72
|
+
const message = messages[index]!;
|
|
73
|
+
return row.id === message.id && row.role === message.role;
|
|
74
|
+
})
|
|
75
|
+
) {
|
|
76
|
+
return prev;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const next = messages.map(({ id, role }) => ({ id, role }));
|
|
80
|
+
prevRowsRef.current = next;
|
|
81
|
+
return next;
|
|
82
|
+
});
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const messageRows = useThreadMessageRows();
|
|
86
|
+
const turns = useMemo(
|
|
87
|
+
() => buildTurns(messageRows),
|
|
88
|
+
[messageRows],
|
|
30
89
|
);
|
|
31
|
-
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Then each virtual turn renders the ids it owns:
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
{turn.messageIds.map((messageId) => (
|
|
96
|
+
<ThreadPrimitive.Unstable_MessageById
|
|
97
|
+
key={messageId}
|
|
98
|
+
messageId={messageId}
|
|
99
|
+
components={MESSAGE_COMPONENTS}
|
|
100
|
+
/>
|
|
101
|
+
))}
|
|
32
102
|
```
|
|
33
103
|
|
|
34
104
|
## Padding spacers, not absolute positioning
|
|
@@ -156,8 +156,9 @@ class MyVoiceAdapter implements RealtimeVoiceAdapter {
|
|
|
156
156
|
|
|
157
157
|
The session status follows the same pattern as other adapters:
|
|
158
158
|
|
|
159
|
-
```
|
|
160
|
-
|
|
159
|
+
```mermaid
|
|
160
|
+
flowchart LR
|
|
161
|
+
starting["starting"] --> running["running"] --> ended["ended"]
|
|
161
162
|
```
|
|
162
163
|
|
|
163
164
|
The `ended` status includes a `reason`:
|
|
@@ -9,18 +9,10 @@ For the adapter contract itself, see [adapters](/docs/runtimes/concepts/adapters
|
|
|
9
9
|
|
|
10
10
|
## How it works
|
|
11
11
|
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
PendingAttachment with the public URL
|
|
17
|
-
│
|
|
18
|
-
composer send ◄────────────────────────────────────┘
|
|
19
|
-
│
|
|
20
|
-
└─► send() emits a content part with the URL
|
|
21
|
-
│
|
|
22
|
-
▼
|
|
23
|
-
AI SDK passes URL to the model
|
|
12
|
+
```mermaid
|
|
13
|
+
flowchart LR
|
|
14
|
+
add["composer add"] --> presign["POST /api/upload<br/>(presign)"] --> put["PUT to object storage"] --> pending["PendingAttachment<br/>with the public URL"]
|
|
15
|
+
pending --> send["composer send"] --> part["send() emits a<br/>content part with the URL"] --> model["AI SDK passes<br/>URL to the model"]
|
|
24
16
|
```
|
|
25
17
|
|
|
26
18
|
Three ideas to internalize before reading the code:
|
|
@@ -9,16 +9,13 @@ This page covers the non-cloud path. AssistantCloud users should see [cloud auth
|
|
|
9
9
|
|
|
10
10
|
## How it works
|
|
11
11
|
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
│
|
|
20
|
-
▼
|
|
21
|
-
scope queries by userId
|
|
12
|
+
```mermaid
|
|
13
|
+
flowchart LR
|
|
14
|
+
browser["browser"] --> routes["/api/auth/[...all]<br/>/api/chat<br/>/api/threads/*"]
|
|
15
|
+
routes --> handlers["better-auth handlers"]
|
|
16
|
+
routes --> session["auth.api.getSession({ headers })"]
|
|
17
|
+
handlers --> session
|
|
18
|
+
session --> uid["session.user.id"] --> scope["scope queries by userId"]
|
|
22
19
|
```
|
|
23
20
|
|
|
24
21
|
Three integration points:
|
|
@@ -9,15 +9,9 @@ If you use AssistantCloud, see [cloud authorization](/docs/cloud/authorization)
|
|
|
9
9
|
|
|
10
10
|
## How it works
|
|
11
11
|
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
│
|
|
16
|
-
▼
|
|
17
|
-
auth() returns { userId }
|
|
18
|
-
│
|
|
19
|
-
▼
|
|
20
|
-
scope queries by userId
|
|
12
|
+
```mermaid
|
|
13
|
+
flowchart LR
|
|
14
|
+
browser["browser"] --> mw["clerkMiddleware<br/>(proxy.ts)"] --> routes["/api/chat<br/>/api/threads/*"] --> auth["auth() returns { userId }"] --> scope["scope queries by userId"]
|
|
21
15
|
```
|
|
22
16
|
|
|
23
17
|
Three places Clerk touches the integration:
|
|
@@ -9,14 +9,9 @@ If you use AssistantCloud, see [cloud authorization](/docs/cloud) instead; cloud
|
|
|
9
9
|
|
|
10
10
|
## How it works
|
|
11
11
|
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
▼
|
|
16
|
-
scope queries by session.user.id
|
|
17
|
-
│
|
|
18
|
-
▼
|
|
19
|
-
database
|
|
12
|
+
```mermaid
|
|
13
|
+
flowchart LR
|
|
14
|
+
browser["browser"] --> routes["/api/chat<br/>/api/threads/*"] --> auth["auth() returns session"] --> scope["scope queries by<br/>session.user.id"] --> db["database"]
|
|
20
15
|
```
|
|
21
16
|
|
|
22
17
|
Three places auth touches the integration:
|
|
@@ -13,11 +13,14 @@ If you arrived here looking to wire up your first chat: jump to [AI SDK v6 quick
|
|
|
13
13
|
|
|
14
14
|
## Where it slots in
|
|
15
15
|
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
```mermaid
|
|
17
|
+
flowchart LR
|
|
18
|
+
client["client"]
|
|
19
|
+
runtime["useChatRuntime (react-ai-sdk)<br/>(thread state · tool calls · attachments<br/>speech · dictation · feedback adapters)"]
|
|
20
|
+
api["/api/chat<br/>(streamText)"]
|
|
21
|
+
provider["provider"]
|
|
22
|
+
|
|
23
|
+
client --> runtime --> api --> provider
|
|
21
24
|
```
|
|
22
25
|
|
|
23
26
|
`@assistant-ui/react-ai-sdk` wraps the AI SDK's `useChat` hook and exposes it as an assistant-ui runtime. The runtime owns conversation state on the client; your `/api/chat` route returns a UI message stream from `streamText`. Everything else (tools, attachments, observability, gateways, custom persistence) layers on top of this base.
|
|
@@ -15,18 +15,11 @@ Integrations are wiring guides for using third-party services with assistant-ui,
|
|
|
15
15
|
|
|
16
16
|
## Where integrations slot in
|
|
17
17
|
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
frameworks │ │ proxies
|
|
24
|
-
(e.g. │ │ (e.g. Helicone,
|
|
25
|
-
Mastra) │ │ Langfuse)
|
|
26
|
-
▼ │
|
|
27
|
-
run on the server, │
|
|
28
|
-
then forward calls ──────┘
|
|
29
|
-
to the provider
|
|
18
|
+
```mermaid
|
|
19
|
+
flowchart LR
|
|
20
|
+
client["client"] --> route["your API route"] --> provider["LLM provider"]
|
|
21
|
+
route -->|"agent frameworks (e.g. Mastra)"| server["run on the server,<br/>then forward calls"]
|
|
22
|
+
server -->|"observability proxies (e.g. Helicone, Langfuse)"| provider
|
|
30
23
|
```
|
|
31
24
|
|
|
32
25
|
Integrations live on the server. **Agent frameworks** like Mastra take over the API route. **Gateways** swap the upstream provider URL. **Observability** logs or traces every call. **Auth** gates the route and scopes per-user data. **Persistence** and **attachments** are adapter recipes for storing chat data outside the default in-memory path.
|
|
@@ -11,10 +11,9 @@ Helicone is independent of which assistant-ui runtime you use. It slots in at th
|
|
|
11
11
|
|
|
12
12
|
## How it works
|
|
13
13
|
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
└─ logs request, response, tokens, cost
|
|
14
|
+
```mermaid
|
|
15
|
+
flowchart LR
|
|
16
|
+
server["your server"] --> proxy["Helicone proxy<br/>(logs request, response, tokens, cost)"] --> provider["OpenAI / Anthropic / etc."]
|
|
18
17
|
```
|
|
19
18
|
|
|
20
19
|
Calls pass through Helicone's edge before reaching the upstream provider. The proxy is transparent: response shape and streaming behavior are unchanged, you just gain a dashboard of every call.
|
|
@@ -13,11 +13,9 @@ Pick Langfuse when you want to see the agent's full call tree inside a single tu
|
|
|
13
13
|
|
|
14
14
|
## How it works
|
|
15
15
|
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
▼
|
|
20
|
-
OpenTelemetry SDK ──► LangfuseSpanProcessor ──► Langfuse
|
|
16
|
+
```mermaid
|
|
17
|
+
flowchart LR
|
|
18
|
+
route["your route"] --> stream["AI SDK streamText<br/>(experimental_telemetry)"] --> otel["OpenTelemetry SDK"] --> proc["LangfuseSpanProcessor"] --> langfuse["Langfuse"]
|
|
21
19
|
```
|
|
22
20
|
|
|
23
21
|
Langfuse subscribes to OpenTelemetry spans the AI SDK already emits when telemetry is enabled. No proxy, no wrapping; the SDK ships spans and Langfuse renders them.
|
|
@@ -15,8 +15,9 @@ This page covers the **AI SDK** path. If you use [`@assistant-ui/react-langgraph
|
|
|
15
15
|
|
|
16
16
|
LangSmith provides a wrapper around the `ai` namespace. You call `wrapAISDK(ai)`, get back the same exports (`generateText`, `streamText`, `generateObject`, `streamObject`), and use those in place of the originals. Every call is then traced.
|
|
17
17
|
|
|
18
|
-
```
|
|
19
|
-
|
|
18
|
+
```mermaid
|
|
19
|
+
flowchart LR
|
|
20
|
+
route["your route"] --> stream["wrapped streamText"] --> client["LangSmith client"] --> langsmith["LangSmith"]
|
|
20
21
|
```
|
|
21
22
|
|
|
22
23
|
## Setup
|
|
@@ -18,9 +18,10 @@ If you're rolling your own auth, replace `auth()` calls with whatever your stack
|
|
|
18
18
|
|
|
19
19
|
## How it works
|
|
20
20
|
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
```mermaid
|
|
22
|
+
flowchart LR
|
|
23
|
+
client["client"] --> threads["/api/threads/*<br/>(RemoteThreadListAdapter)"] --> tt["threads table"]
|
|
24
|
+
client --> messages["/api/messages/*<br/>(ThreadHistoryAdapter)"] --> mt["messages table"]
|
|
24
25
|
```
|
|
25
26
|
|
|
26
27
|
Two adapters, two tables:
|
|
@@ -103,6 +103,14 @@ import { ComposerPrimitive } from "@assistant-ui/react";
|
|
|
103
103
|
|
|
104
104
|
The primitive's behavior (keyboard handling, disabled state, form submission) is merged onto your element. Your styles, your component, primitive wiring.
|
|
105
105
|
|
|
106
|
+
<Callout type="info">
|
|
107
|
+
Own the input DOM entirely, such as a `contentEditable` surface or editor
|
|
108
|
+
library that cannot be expressed through `asChild` or `render`? See
|
|
109
|
+
[Headless Composer Input](/docs/guides/headless-composer-input) for the
|
|
110
|
+
unstable hook that supplies composer text and send gating without
|
|
111
|
+
`ComposerPrimitive.Input`.
|
|
112
|
+
</Callout>
|
|
113
|
+
|
|
106
114
|
### Unstable Trigger Popovers
|
|
107
115
|
|
|
108
116
|
Composer includes an unstable **trigger popover** system for character-triggered popovers (e.g. `@` for mentions, `/` for slash commands). Multiple triggers coexist under a single `TriggerPopoverRoot`.
|
|
@@ -319,6 +319,30 @@ Renders a single message at a specific index in the thread.
|
|
|
319
319
|
|
|
320
320
|
<PrimitivesTypeTable type="ThreadPrimitiveMessageByIndexProps" parameters={ThreadPrimitiveDocs.MessageByIndex.props} />
|
|
321
321
|
|
|
322
|
+
### Unstable_MessageById
|
|
323
|
+
|
|
324
|
+
Renders a single message by id with the same `components` surface as
|
|
325
|
+
`MessageByIndex`. Pair it with `unstable_useThreadMessageIds` for virtualized or
|
|
326
|
+
custom message lists that should stay attached to messages across reordering and
|
|
327
|
+
windowing. Unknown ids render `null`.
|
|
328
|
+
|
|
329
|
+
```tsx
|
|
330
|
+
const messageIds = unstable_useThreadMessageIds();
|
|
331
|
+
|
|
332
|
+
messageIds.map((messageId) => (
|
|
333
|
+
<ThreadPrimitive.Unstable_MessageById
|
|
334
|
+
key={messageId}
|
|
335
|
+
messageId={messageId}
|
|
336
|
+
components={{ Message: MyMessage }}
|
|
337
|
+
/>
|
|
338
|
+
));
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
<Callout type="warn">
|
|
342
|
+
`unstable_useThreadMessageIds` and `ThreadPrimitive.Unstable_MessageById` are
|
|
343
|
+
experimental and may change in any release.
|
|
344
|
+
</Callout>
|
|
345
|
+
|
|
322
346
|
### ScrollToBottom
|
|
323
347
|
|
|
324
348
|
Scrolls the viewport to the bottom. Automatically disabled when already at the bottom. Renders a `<button>` element unless `asChild` is set.
|
|
@@ -68,6 +68,8 @@ time, the same way a live run never stores them.
|
|
|
68
68
|
|
|
69
69
|
`fromAgUiMessages` reconstructs the text, reasoning, and tool calls of each message. Multimodal user input (`image`, `audio`, `video`, and `document` parts, as well as legacy `binary` parts) is restored as attachments on the user message, so a backend that persists multimodal messages shows them again on reload and re-sends them on the next run. Legacy `binary` parts that only reference a file id are not restored.
|
|
70
70
|
|
|
71
|
+
An assistant message whose tool call has no matching tool result is reconstructed with `requires-action` status, the same status the runtime derives for a pending tool call, so a reloaded human-in-the-loop call (for example an `ask_user` tool) is actionable rather than stuck. This matches how every other external-store runtime surfaces a pending tool call on reload. The AG-UI wire snapshot carries no run outcome, so a tool call that a successful run intentionally left without a result is also surfaced as actionable.
|
|
72
|
+
|
|
71
73
|
## Thread list (experimental)
|
|
72
74
|
|
|
73
75
|
<Callout type="warn">
|
|
@@ -131,6 +133,25 @@ await runtime.unstable_submitInterruptResponses(
|
|
|
131
133
|
);
|
|
132
134
|
```
|
|
133
135
|
|
|
136
|
+
### Steering away from an interrupt
|
|
137
|
+
|
|
138
|
+
When the user ignores the interrupt UI and just sends a new message, use the `useAgUiSteerAway` hook. Every open interrupt defaults to `status: "cancelled"`, the new message is appended, and the run resumes with `resume: ResumeEntry[]` on the wire. With no pending interrupts it behaves like a normal append.
|
|
139
|
+
|
|
140
|
+
```tsx
|
|
141
|
+
const steerAway = useAgUiSteerAway();
|
|
142
|
+
|
|
143
|
+
// the user typed a new message instead of answering the interrupt
|
|
144
|
+
await steerAway("actually, let's do something else");
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
The message accepts a plain string or a partial `AppendMessage` (the parent defaults to the current head, which is the interrupted assistant message). Pass `responses` to override the status of specific interrupts; the rest still default to cancelled.
|
|
148
|
+
|
|
149
|
+
```tsx
|
|
150
|
+
await steerAway("continue without the file", [
|
|
151
|
+
{ interruptId: "tool-1", status: "resolved", payload: { approved: true } },
|
|
152
|
+
]);
|
|
153
|
+
```
|
|
154
|
+
|
|
134
155
|
## Supported events
|
|
135
156
|
|
|
136
157
|
The runtime parses the AG-UI event stream and maps each event type to assistant-ui state.
|
|
@@ -299,6 +299,54 @@ runtime.thread.import(repo);
|
|
|
299
299
|
|
|
300
300
|
Each message must have an explicit `id` and `parentId`; messages with the same `parentId` create branches. Parents must appear before children in the array.
|
|
301
301
|
|
|
302
|
+
### Exporting a snapshot
|
|
303
|
+
|
|
304
|
+
`thread.import()` has a counterpart, `thread.export()`, which captures the current thread (including its full branch tree) as a serializable `ExportedMessageRepository`. Use it to persist a conversation and re-import it later:
|
|
305
|
+
|
|
306
|
+
```tsx
|
|
307
|
+
// capture the current thread as a serializable snapshot
|
|
308
|
+
const repo = runtime.thread.export();
|
|
309
|
+
await saveToBackend(JSON.stringify(repo));
|
|
310
|
+
|
|
311
|
+
// later, restore it into a runtime
|
|
312
|
+
runtime.thread.import(repo);
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
The exported shape round-trips through `thread.import()` directly, so the same value is both your persistence format and what you load back.
|
|
316
|
+
|
|
317
|
+
### Persisting branch selection
|
|
318
|
+
|
|
319
|
+
If you store the full branch tree outside assistant-ui, persist the selected branch head too and pass it back as `messageRepository.headId`. `setMessages` still performs the branch switch; `unstable_onBranchChange` is an additional signal that fires after an explicit `switchToBranch` action, such as a BranchPicker click.
|
|
320
|
+
|
|
321
|
+
```tsx
|
|
322
|
+
const runtime = useExternalStoreRuntime({
|
|
323
|
+
messageRepository: {
|
|
324
|
+
messages: storedMessages,
|
|
325
|
+
headId: selectedHeadId,
|
|
326
|
+
},
|
|
327
|
+
setMessages: (messages) => {
|
|
328
|
+
setVisibleMessages(messages);
|
|
329
|
+
},
|
|
330
|
+
unstable_onBranchChange: ({ headId, visibleMessageIds }) => {
|
|
331
|
+
saveSelectedBranch({
|
|
332
|
+
headId,
|
|
333
|
+
visibleMessageIds,
|
|
334
|
+
});
|
|
335
|
+
},
|
|
336
|
+
onNew,
|
|
337
|
+
});
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
`headId` is the canonical persisted head of the visible branch. Optimistic or transient message ids are not surfaced there. `visibleMessageIds` is the currently visible path in order, which can include an optimistic leaf while `headId` points to its persisted ancestor.
|
|
341
|
+
|
|
342
|
+
The callback only fires for explicit branch switches, and consecutive switches that resolve to the same canonical head are de-duped. It does not fire on adapter resync, `messageRepository` reset, append, edit/regenerate, content-only updates, or while the thread is running.
|
|
343
|
+
|
|
344
|
+
<Callout type="warn">
|
|
345
|
+
`unstable_onBranchChange` is under active development and may change without
|
|
346
|
+
notice. It complements `setMessages`; it does not enable branch switching by
|
|
347
|
+
itself.
|
|
348
|
+
</Callout>
|
|
349
|
+
|
|
302
350
|
## Tool calling
|
|
303
351
|
|
|
304
352
|
Handle tool results by updating the matching tool-call entry:
|
|
@@ -717,6 +765,12 @@ useExternalStoreRuntime({
|
|
|
717
765
|
type: "(messages: readonly T[]) => void",
|
|
718
766
|
description: "Update messages (required for branch switching).",
|
|
719
767
|
},
|
|
768
|
+
{
|
|
769
|
+
name: "unstable_onBranchChange",
|
|
770
|
+
type: "(event: ExternalStoreBranchChange) => void",
|
|
771
|
+
description:
|
|
772
|
+
"Called after an explicit branch switch with the canonical persisted head id and visible message path. Complements setMessages and is unstable.",
|
|
773
|
+
},
|
|
720
774
|
{
|
|
721
775
|
name: "onEdit",
|
|
722
776
|
type: "(message: AppendMessage) => Promise<void>",
|